In this document we present the joint analysis of the PASS1A metabolomics datasets.

Load all datasets

Load the data from the cloud, including: phenotypic data, metabolomic datasets, and metabolomics dictionary.

source("~/Desktop/repos/motrpac-bic-norm-qc/tools/supervised_normalization_functions.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/unsupervised_normalization_functions.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/gcp_functions.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/association_analysis_methods.R")
source("~/Desktop/repos/motrpac-bic-norm-qc/tools/data_aux_functions.R")
source("~/Desktop/repos/motrpac/tools/prediction_ml_tools.R")
library(randomForest) # for classification tests
# Load the dmaqc data
merged_dmaqc_data =  load_from_bucket("merged_dmaqc_data2019-10-15.RData",
    "gs://bic_data_analysis/pass1a/pheno_dmaqc/",F)
merged_dmaqc_data = merged_dmaqc_data[[1]]
rownames(merged_dmaqc_data) = as.character(merged_dmaqc_data$vial_label)
# define the tissue variable
merged_dmaqc_data$tissue = merged_dmaqc_data$sampletypedescription
# define the time to freeze variable
merged_dmaqc_data$time_to_freeze = merged_dmaqc_data$calculated.variables.time_death_to_collect_min + 
  merged_dmaqc_data$calculated.variables.time_collect_to_freeze_min
# col time vs. control
# df = data.frame(
#   bid = merged_dmaqc_data$bid,
#   edta_col_time = merged_dmaqc_data$calculated.variables.edta_coll_time,
#   time_to_freeze = merged_dmaqc_data$time_to_freeze,
#   is_control = merged_dmaqc_data$animal.key.is_control,
#   tp = merged_dmaqc_data$animal.key.timepoint,
#   tissue = merged_dmaqc_data$specimen.processing.sampletypedescription
# )
# df = unique(df)
# boxplot(edta_col_time/3600 ~ is_control,df)
# boxplot(edta_col_time/3600 - tp ~ is_control,df)
# wilcox.test(edta_col_time/3600 ~ is_control,df)
# blood freeze times
blood_samples = 
  merged_dmaqc_data$specimen.processing.sampletypedescription ==
  "EDTA Plasma"
blood_freeze_time = 
  as.difftime(merged_dmaqc_data$specimen.processing.t_freeze,units = "mins") -
  as.difftime(merged_dmaqc_data$specimen.processing.t_edtaspin,units="mins")
blood_freeze_time = as.numeric(blood_freeze_time)
time_to_freeze = merged_dmaqc_data$time_to_freeze[blood_samples] = 
  blood_freeze_time[blood_samples]
# Load our parsed metabolomics datasets
metabolomics_bucket_obj = load_from_bucket(
  file = "metabolomics_parsed_datasets_pass1a_external1.RData",
  bucket = "gs://bic_data_analysis/pass1a/metabolomics/")
metabolomics_parsed_datasets = metabolomics_bucket_obj$metabolomics_parsed_datasets

Define the variables to be adjusted for:

biospec_cols = c(
  "acute.test.distance",
  "calculated.variables.time_to_freeze",
  # "calculated.variables.edta_coll_time", # no need - see code above for blood
  "bid" # required for matching datasets
  )
differential_analysis_cols = c(
  "animal.registration.sex",
  "animal.key.timepoint",
  "animal.key.is_control"
)
pipeline_qc_cols = c("sample_order")

Log-transform: effect on variance

Some sites do not use the log transformation on their dataset. In this section we plot the coefficient of variation as a function of the mean instensity. We take a single dataset as an example to show how log-transformed data have reduced dependency and smoother plots.

As an additional analysis we also plot the number of missing values per metabolite as a function of its mean intensity. We show that while there is high correlation some missing values appear in fairely high intensities. This is important for imputation as some sites use some fixed low value instead of knn imputation.


# Plot cv vs means
library(gplots)
d = metabolomics_parsed_datasets[["white_adipose_powder,metab_u_hilicpos,unnamed"]]
dx = d$sample_data
CoV<-function(x){return(sd(x,na.rm = T)/mean(x,na.rm=T))}
dmeans = apply(dx,1,mean,na.rm=T)
CoVs = apply(dx,1,CoV)
inds = !is.na(CoVs)
df = data.frame(Mean_intensity = dmeans[inds],CoV = CoVs[inds])
plot(CoV~Mean_intensity,df,cex=0.5,pch=20,main="Raw data")
lines(lowess(CoV~Mean_intensity,df),lty=2,lwd=2,col="blue")

# Repeat after log2
dx = log(1+d$sample_data,base=2)
dmeans = apply(dx,1,mean,na.rm=T)
CoVs = apply(dx,1,CoV)
inds = !is.na(CoVs)
df = data.frame(Mean_intensity = dmeans[inds],CoV = CoVs[inds])
plot(CoV~Mean_intensity,df,cex=0.5,pch=20,main="Log2 data")
lines(lowess(CoV~Mean_intensity,df),lty=2,lwd=2,col="blue")

# Plot number of NAs vs intensity mean
dx = log(1+d$sample_data,base=2)
dmeans = apply(dx,1,mean,na.rm=T)
num_nas = rowSums(is.na(dx))
df = data.frame(Num_NAs = num_nas[inds],Mean_intensity = dmeans[inds])
rho = cor(df$Num_NAs,df$Mean_intensity,method="spearman")
rho = format(rho,digits=2)
plot(Num_NAs~Mean_intensity,df,cex=0.5,pch=20,
     main=paste("Spearman:",rho))

Load the preprocessed and normalized data

metabolomics_processed_datasets = load_from_bucket(
  file="metabolomics_processed_datasets11182019.RData",
  bucket = "gs://bic_data_analysis/pass1a/metabolomics/"
)[[1]]
Copying gs://bic_data_analysis/pass1a/metabolomics/metabolomics_processed_datasets11182019.RData...
/ [0 files][    0.0 B/480.7 MiB]                                                
-
- [0 files][    0.0 B/480.7 MiB]                                                
\
\ [0 files][  6.2 MiB/480.7 MiB]                                                
|
/
/ [0 files][ 16.8 MiB/480.7 MiB]                                                
-
\
\ [0 files][ 27.3 MiB/480.7 MiB]                                                
|
| [0 files][ 38.2 MiB/480.7 MiB]                                                
/
-
- [0 files][ 49.0 MiB/480.7 MiB]                                                
\
|
| [0 files][ 58.8 MiB/480.7 MiB]                                                
/
/ [0 files][ 69.4 MiB/480.7 MiB]                                                
-
\
\ [0 files][ 80.2 MiB/480.7 MiB]                                                
|
/
/ [0 files][ 91.0 MiB/480.7 MiB]                                                
-
- [0 files][101.3 MiB/480.7 MiB]   10.2 MiB/s                                   
\
|
| [0 files][110.6 MiB/480.7 MiB]   10.1 MiB/s                                   
/
-
- [0 files][120.9 MiB/480.7 MiB]   10.0 MiB/s                                   
\
\ [0 files][131.2 MiB/480.7 MiB]   10.0 MiB/s                                   
|
/
/ [0 files][141.3 MiB/480.7 MiB]    9.9 MiB/s                                   
-
\
\ [0 files][151.9 MiB/480.7 MiB]    9.9 MiB/s                                   
|
| [0 files][162.2 MiB/480.7 MiB]   10.1 MiB/s                                   
/
-
- [0 files][172.7 MiB/480.7 MiB]   10.3 MiB/s                                   
\
\ [0 files][183.3 MiB/480.7 MiB]   10.3 MiB/s                                   
|
/
/ [0 files][193.4 MiB/480.7 MiB]   10.3 MiB/s                                   
-
\
\ [0 files][203.4 MiB/480.7 MiB]   10.2 MiB/s                                   
|
| [0 files][213.0 MiB/480.7 MiB]   10.0 MiB/s                                   
/
-
- [0 files][223.5 MiB/480.7 MiB]   10.0 MiB/s                                   
\
|
| [0 files][233.8 MiB/480.7 MiB]   10.0 MiB/s                                   
/
/ [0 files][243.4 MiB/480.7 MiB]    9.9 MiB/s                                   
-
\
\ [0 files][252.9 MiB/480.7 MiB]    9.8 MiB/s                                   
|
/
/ [0 files][263.0 MiB/480.7 MiB]    9.9 MiB/s                                   
-
- [0 files][273.5 MiB/480.7 MiB]    9.8 MiB/s                                   
\
|
| [0 files][283.6 MiB/480.7 MiB]    9.8 MiB/s                                   
/
/ [0 files][293.1 MiB/480.7 MiB]    9.8 MiB/s                                   
-
\
\ [0 files][299.2 MiB/480.7 MiB]    9.1 MiB/s                                   
|
/
/ [0 files][307.7 MiB/480.7 MiB]    8.7 MiB/s                                   
-
- [0 files][317.2 MiB/480.7 MiB]    8.5 MiB/s                                   
\
|
| [0 files][327.3 MiB/480.7 MiB]    8.5 MiB/s                                   
/
-
- [0 files][337.9 MiB/480.7 MiB]    8.7 MiB/s                                   
\
\ [0 files][347.7 MiB/480.7 MiB]    9.5 MiB/s                                   
|
/
/ [0 files][357.7 MiB/480.7 MiB]    9.8 MiB/s                                   
-
\
\ [0 files][367.8 MiB/480.7 MiB]   10.0 MiB/s                                   
|
| [0 files][377.6 MiB/480.7 MiB]   10.0 MiB/s                                   
/
-
- [0 files][387.6 MiB/480.7 MiB]    9.9 MiB/s                                   
\
|
| [0 files][397.7 MiB/480.7 MiB]    9.8 MiB/s                                   
/
/ [0 files][408.0 MiB/480.7 MiB]    9.9 MiB/s                                   
-
\
\ [0 files][418.1 MiB/480.7 MiB]    9.8 MiB/s                                   
|
| [0 files][428.6 MiB/480.7 MiB]   10.0 MiB/s                                   
/
-
- [0 files][438.8 MiB/480.7 MiB]   10.0 MiB/s                                   
\
|
| [0 files][448.9 MiB/480.7 MiB]   10.0 MiB/s                                   
/
/ [0 files][459.2 MiB/480.7 MiB]   10.3 MiB/s                                   
-
\
\ [0 files][469.2 MiB/480.7 MiB]   10.1 MiB/s                                   
|
/
/ [0 files][478.5 MiB/480.7 MiB]    9.9 MiB/s                                   
/ [1 files][480.7 MiB/480.7 MiB]    9.3 MiB/s                                   

Operation completed over 1 objects/480.7 MiB.                                    
# Reduce the metadata to the selected columns
for(currname in names(metabolomics_processed_datasets)){
  curr_data = metabolomics_processed_datasets[[currname]]$normalized_data[[1]]
  # organize the metadata
  curr_meta = merged_dmaqc_data[colnames(curr_data),
        union(biospec_cols,differential_analysis_cols)]
  # remove metadata variables with too many NAs
  na_counts = apply(is.na(curr_meta),2,sum)
  curr_meta = curr_meta[,na_counts/nrow(curr_meta) < 0.1]
  metabolomics_processed_datasets[[currname]]$sample_meta_parsed = curr_meta
}

Log-transform: effect on differential analysis in targeted data

Untargeted data are typically log-transformed and analyzed using linear models. On the other hand, concentration data are sometimes analyzed with the same type of models but using the original data. This raises a problem if we wish to compare exact statistics from these data. In this section we perform residual analysis for single metabolites. Our goal is to identify if concentration data behaves “normally” when not log-transformed. The analysis below examines the residuals of the data after fitting linear models for each metabolite, adjusting for freeze time and sex. We then compare the results with and without the log-transformation, counting the number of metabolites with a significant evidence for non-normally distributed residuals.

# check for normality using the Kolmogorov-Smirnov test
is_normal_test<-function(v){
  if(sd(v,na.rm = T)==0){return(0)}
  try({return(shapiro.test(v)$p.value)})
  # The Shapiro test may fail if the sd of v is zero
  return(ks.test(v,"pnorm",mean(v,na.rm=T),sd(v,na.rm = T))$p.value)
}
# go over the named datasets, get a logged and an unlogged version of
# the data, use these as inputs for the regression
residual_analysis_results = list()
for(nn1 in names(metabolomics_processed_datasets)){
  if(!metabolomics_processed_datasets[[nn1]]$is_targeted){next}
  x_log = metabolomics_processed_datasets[[nn1]]$normalized_data[[1]]
  x_unlog = 2^x_log
  
  # take the covariates, ignore distances
  x_meta = unique(metabolomics_processed_datasets[[nn1]]$sample_meta_parsed)
  curr_covs = x_meta[,intersect(colnames(x_meta),biospec_cols[2])]
  curr_covs = data.frame(curr_covs,
           sex=x_meta$animal.registration.sex)
  
  # get the lm objects
  curr_models = list()
  for(tp in unique(x_meta$animal.key.timepoint)){
      res_log = apply(
        x_log,1,
        pass1a_simple_differential_abundance,
        tps = x_meta$animal.key.timepoint,tp=tp,
        is_control = x_meta$animal.key.is_control,
        covs = curr_covs,return_model=T
      )
      res_unlog = apply(
        x_unlog,1,
        pass1a_simple_differential_abundance,
        tps = x_meta$animal.key.timepoint,tp=tp,
        is_control = x_meta$animal.key.is_control,
        covs = curr_covs,return_model=T
      )
      is_norm = cbind(
        sapply(res_log,function(x)is_normal_test(residuals(x))),
        sapply(res_unlog,function(x)is_normal_test(residuals(x)))
      )
      colnames(is_norm) = c("log","not log")
      curr_models[[as.character(tp)]] = is_norm
      
      # # test a specific model
      # ind = 246
      # plot(x_unlog[ind,],x_log[ind,])
      # 
      # resids_log = rstandard(res_log[[ind]])
      # fit_log = res_log[[ind]]$fitted.values
      # resids_unlog = rstandard(res_unlog[[ind]])
      # fit_unlog = res_unlog[[ind]]$fitted.values
      # 
      # plot(fit_log,resids_log)
      # plot(fit_unlog,resids_unlog)
  }
  residual_analysis_results[[nn1]] = curr_models
}

# Is there a significant difference between the two options?
log_vs_unlog_summ_mat = sapply(residual_analysis_results,
    function(x)sapply(x,
        function(y)
          wilcox.test(y[,1],y[,2],paired = T,alternative = "g")$p.value))

# Count the number of non-normal metabolites
num_nonnormal_log = sapply(residual_analysis_results,
    function(x)sapply(x,
        function(y)sum(y[,1]<0.05)))
num_nonnormal_log = 
  num_nonnormal_log[,order(colnames(num_nonnormal_log))]
num_nonnormal_unlog = sapply(residual_analysis_results,
    function(x)sapply(x,
        function(y)sum(y[,2]<0.05)))
num_nonnormal_unlog = 
  num_nonnormal_unlog[,order(colnames(num_nonnormal_unlog))]

library(corrplot)
par(mar = c(5,5,5,10))
normdiffs = t(num_nonnormal_log)- t(num_nonnormal_unlog)
corrplot(normdiffs,is.corr = F,tl.cex = 0.7)

Trageted vs. Untargeted: single metabolite comparison

Compute statistics for each dataset

Compare overlaps, effect sizes, and correlations within tissues. Compare targeted-untargeted pairs only. For differential analysis we use the same model as in the analysis above.

# Transform the data matrix to have compound names as row names.
# This requires removing rows without names and changing the rownames of
# the input matrix x.
# Also, do not assume that the row annotation orde fits the data necessarily
extract_by_comp_name_from_row_annot<-function(x,row_annot_x){
  # get the column that has the row names of x or at least
  # have the greatest intersection with it
  int_sizes = apply(row_annot_x,2,function(x,y)length(intersect(x,y)),y=rownames(x))
  ind = which(int_sizes==max(int_sizes,na.rm = T))[1]
  # update the annotation table to have only rows that have 
  # a row in x and then update the rownames to be from the 
  # selected column
  row_annot_x = row_annot_x[is.element(row_annot_x[,ind],set=rownames(x)),]
  rownames(row_annot_x) = row_annot_x[,ind]
  # we can now intersect x and the annotation using their row names
  # and update x accordingly
  shared = intersect(rownames(row_annot_x),rownames(x))
  x = x[shared,]
  row_annot_x = row_annot_x[shared,]
  rownames(x) = row_annot_x$motrpac_comp_name
  return(x)
}
tar_vs_untar_norm_pairs = list(
  c("log2,imp","log2,imp,TMM"),
  c("log2,imp","log2,imp"),
  c("log2,imp","med,log,imp"),
  c("imp,none","imp,none"),
  c("imp,none","imp,TMM"),
  c("imp,none","med,log,imp")
)
# The loop below is the core of the computations for comparing 
# targeted and untargeted data when using known compound names
#
# For each normalization option (a pair from the list above) we compute
# all pairwise correlation matrices of the overlapping metabolites and
# the differential analysis results (again of the overlap).
# These objects are then used later for other quantitative comparisons
norm_method2comparison_results = list()
for(norm_pairs in tar_vs_untar_norm_pairs){
  tar_norm_method = norm_pairs[1]
  untar_norm_method = norm_pairs[2]
  single_metabolite_corrs = list()
  single_metabolite_de = c()
  named2covered_shared_metabolites = list()
  for(nn1 in names(metabolomics_processed_datasets)){
    nn1_tissue = metabolomics_processed_datasets[[nn1]]$tissue
    if(!metabolomics_processed_datasets[[nn1]]$is_targeted){next}
    single_metabolite_corrs[[nn1]] = list()
    named2covered_shared_metabolites[[nn1]] = NULL
    for(nn2 in names(metabolomics_processed_datasets)){
      if(nn2 == nn1){next}
      if(metabolomics_processed_datasets[[nn2]]$is_targeted){next}
      nn2_tissue = metabolomics_processed_datasets[[nn2]]$tissue
      nn2_dataset = paste(strsplit(nn2,split=",")[[1]][3:4],collapse = ",")
      if(nn1_tissue!=nn2_tissue){next}
      # get the numeric datasets and their annotation
      x = metabolomics_processed_datasets[[nn1]]$normalized_data[[tar_norm_method]]
      y = metabolomics_processed_datasets[[nn2]]$normalized_data[[untar_norm_method]]
      row_annot_x = metabolomics_processed_datasets[[nn1]]$row_annot
      row_annot_y = metabolomics_processed_datasets[[nn2]]$row_annot
      # transform metabolite names to the motrpac comp name
      x = extract_by_comp_name_from_row_annot(x,row_annot_x)
      y = extract_by_comp_name_from_row_annot(y,row_annot_y)
      # align the sample sets
      bid_y = merged_dmaqc_data[colnames(y),"bid"]
      bid_x = merged_dmaqc_data[colnames(x),"bid"]    
      # step 1: merge samples from the same BID
      if(length(unique(bid_x))!=length(bid_x)){
        x = aggregate_repeated_samples(x,bid_x)
      }
      else{
        colnames(x) = bid_x
      }
      if(length(unique(bid_y))!=length(bid_y)){
        y = aggregate_repeated_samples(y,bid_y)
      }else{
        colnames(y) = bid_y
      }
      # step 2: use the shared bio ids
      shared_bids = as.character(intersect(colnames(y),colnames(x)))
      x = as.matrix(x[,shared_bids])
      y = as.matrix(y[,shared_bids])
      # At this point x and y are over the same BIDs, now we add the metadata
      y_meta = 
        unique(metabolomics_processed_datasets[[nn1]]$sample_meta_parsed)
      rownames(y_meta) = y_meta$bid
      y_meta = y_meta[shared_bids,]
      
      # If one dataset is log-transformed and the other is not
      # transform back to the original values
      is_tar_log = grepl("log",tar_norm_method)
      is_untar_log = grepl("log",untar_norm_method)
      if(is_tar_log && !is_untar_log){
        x = 2^x
      }
      if(!is_tar_log && is_untar_log){
        y = 2^y
      }
    
      # If data are not log-transformed then scale the rows
      if(!is_tar_log || !is_untar_log){
        x = t(scale(t(x),center = T,scale = T))
        y = t(scale(t(y),center = T,scale = T))
      }
    
      # get the shared matebolites
      shared_metabolites = intersect(rownames(x),rownames(y))
      shared_metabolites = na.omit(shared_metabolites)
      if(length(shared_metabolites)==0){next}
      named2covered_shared_metabolites[[nn1]] = union(
        named2covered_shared_metabolites[[nn1]],
        shared_metabolites
      )
    
      # Compute the correlation matrices of the shared metabolites
      if(length(shared_metabolites)>1){
          corrs =cor(t(x[shared_metabolites,]),
                t(y[shared_metabolites,]),method = "spearman")
      }
      else{
          corrs = cor(x[shared_metabolites,],
                y[shared_metabolites,],method = "spearman")
      }
    
      # take the covariates (ignore distances)
      curr_cov_cols = intersect(colnames(y_meta),biospec_cols[2])
      curr_covs = data.frame(y_meta[,curr_cov_cols])
      names(curr_covs) = curr_cov_cols
      curr_covs$sex = y_meta$animal.registration.sex # add sex
    
      # differential analysis
      for(tp in unique(y_meta$animal.key.timepoint)){
        curr_control_tp = NULL
        # if(tp == 7 || tp == 4){curr_control_tp=7}
        resx = t(apply(
          matrix(x[shared_metabolites,],nrow=length(shared_metabolites)),1,
          pass1a_simple_differential_abundance,
          tps = y_meta$animal.key.timepoint,tp=tp,
          is_control = y_meta$animal.key.is_control,
          covs = curr_covs,return_model=F,
          control_tp = curr_control_tp
        ))
        resy = t(apply(
          matrix(y[shared_metabolites,],nrow=length(shared_metabolites)),1,
          pass1a_simple_differential_abundance,
          tps = y_meta$animal.key.timepoint,tp=tp,
          is_control = y_meta$animal.key.is_control,
          covs = curr_covs,return_model=F,
          control_tp = curr_control_tp
        ))
        # Add dataset information, time point, tissue
        # These are important annotations for our summary matrix
        # called single_metabolite_de below
        added_columns = matrix(cbind(
          rep(nn1,length(shared_metabolites)),
          rep(nn2,length(shared_metabolites)),
          shared_metabolites,
          rep(tp,length(shared_metabolites)),
          rep(nn1_tissue,length(shared_metabolites))
        ),nrow=length(shared_metabolites))
        resx = cbind(resx,rep(T,nrow(resx)))
        colnames(resx)[ncol(resx)] = "is_targeted"
        resy = cbind(resy,rep(F,nrow(resy)))
        colnames(resy)[ncol(resy)] = "is_targeted"
        if(nrow(resx)>1){
          resx = cbind(added_columns[,-2],resx)
          resy = cbind(added_columns[,-1],resy)
        }
        else{
          resx = c(added_columns[,-2],resx)
          resy = c(added_columns[,-1],resy)
       }
        single_metabolite_de = rbind(single_metabolite_de,resx)
        single_metabolite_de = rbind(single_metabolite_de,resy)
      }
      single_metabolite_corrs[[nn1]][[nn2]] = corrs
    }
  }
  # Reformat the differential analysis results for easier comparison later
  single_metabolite_de = data.frame(single_metabolite_de)
  names(single_metabolite_de) = c("dataset","metabolite","tp","tissue",
    "Est","Std","Tstat","Pvalue","is_targeted")
  for(col in names(single_metabolite_de)[-c(1:4)]){
    single_metabolite_de[[col]] = as.numeric(
      as.character(single_metabolite_de[[col]]))
  }
  for(col in names(single_metabolite_de)[1:4]){
    single_metabolite_de[[col]] = 
      as.character(single_metabolite_de[[col]])
  }
  # Remove duplications
  # Rounding the p-values - necessary for removing duplicates
  # using the unique function
  rownames(single_metabolite_de) = NULL
  for(nn in names(single_metabolite_de)){
    ndig = 5
    if(grepl("pval",nn,ignore.case = T)){
      ndig = 10
    }
    if(is.numeric(single_metabolite_de[[nn]])){
      single_metabolite_de[[nn]] = 
      round(single_metabolite_de[[nn]],digits = ndig)
    }
  }
  single_metabolite_de = unique(single_metabolite_de)
  
  # Finally, store all the results for the current normalization pair
  pair_name = paste(tar_norm_method,untar_norm_method,sep=";")
  norm_method2comparison_results[[pair_name]] = list(
    single_metabolite_de = single_metabolite_de,
    single_metabolite_corrs = single_metabolite_corrs,
    named2covered_shared_metabolites = named2covered_shared_metabolites
  )
}

We next transform the data above into tables that contain data for each combination of metabolite, time point, and tissue. These are then used for different meta-analyses: (1) a simple random effects analysis, (2) random effects with a binary covariate indicating if a dataset is targeted or untargeted, (3) redo the RE model of (1) with the targeted data only, and (4) redo the RE model of (1) with the untargeted data only.

library(metafor)
for(pair_name in names(norm_method2comparison_results)){
  meta_analysis_stats = list()
  single_metabolite_de = 
    norm_method2comparison_results[[pair_name]]$single_metabolite_de
  for(tissue in unique(single_metabolite_de$tissue)){
    for(tp in unique(single_metabolite_de$tp)){
      curr_subset = single_metabolite_de[
        single_metabolite_de$tissue==tissue &
          single_metabolite_de$tp==tp,]
      for(metabolite in unique(curr_subset$metabolite)){
        curr_met_data = curr_subset[
          curr_subset$metabolite==metabolite,]
        curr_met_data$var = curr_met_data$Std^2
        re_model1 = NULL;re_model2=NULL
        re_model_tar = NULL;re_model_untar = NULL
        try({re_model1 = rma.uni(curr_met_data$Est,curr_met_data$var,method="FE")})
        try({re_model2 = rma.mv(curr_met_data$Est,curr_met_data$var,
                           mods=curr_met_data$is_targeted,method="FE")})
        try({re_model_tar = rma.uni(
          curr_met_data[curr_met_data$is_targeted==1,"Est"],
          curr_met_data[curr_met_data$is_targeted==1,"var"],
          method="FE"
        )})
        try({re_model_untar = rma.uni(
          curr_met_data[curr_met_data$is_targeted==0,"Est"],
          curr_met_data[curr_met_data$is_targeted==0,"var"],
          method="FE"
        )})
        meta_analysis_stats[[paste(metabolite,tissue,tp,sep=",")]] = 
          list(curr_met_data=curr_met_data,re_model1=re_model1,
            re_model2 = re_model2,re_model_tar=re_model_tar,
            re_model_untar = re_model_untar)
      }
    }
  }
  norm_method2comparison_results[[pair_name]]$meta_analysis_stats =
    meta_analysis_stats
}

Targeted vs. Untargeted: compound overlap

We first plot the number and percentage of metabolites in the targeted datasets that are measured in at least one untargeted dataset.

library(ggplot2)
dataset2num_metabolites = sapply(metabolomics_processed_datasets, 
                                 function(x)nrow(x$sample_data))
named_dataset_coverage = sapply(named2covered_shared_metabolites,length)
named_dataset_coverage = data.frame(
  name = names(named_dataset_coverage),
  percentage = named_dataset_coverage /
  dataset2num_metabolites[names(named_dataset_coverage)],
  count = named_dataset_coverage,
  total = dataset2num_metabolites[names(named_dataset_coverage)]
)
# add datasets with no coverage
all_targeted_datasets = names(metabolomics_processed_datasets)
all_targeted_datasets = all_targeted_datasets[!grepl("untar",all_targeted_datasets)]
zero_coverage_datasets = setdiff(all_targeted_datasets,
                                 named_dataset_coverage$name)
zero_coverage_datasets = data.frame(
  name = zero_coverage_datasets,
  percentage = rep(0,length(zero_coverage_datasets)),
  count = rep(0,length(zero_coverage_datasets)),
  total = dataset2num_metabolites[zero_coverage_datasets]
)
named_dataset_coverage = rbind(named_dataset_coverage,
                           zero_coverage_datasets)
named_dataset_coverage = 
  named_dataset_coverage[order(as.character(named_dataset_coverage$name)),]
print(ggplot(named_dataset_coverage, aes(x=name, y=percentage)) + 
  geom_bar(stat = "identity",width=0.2) + coord_flip() +
  geom_text(data=named_dataset_coverage, 
            aes(name, percentage+0.05, label=count), 
            position = position_dodge(width=0.9),
            size=4) + 
  ggtitle("Targeted dataset: coverage by untargeted"))

Comparison results: normalization methods

Spearman correlations

Compare the normalization methods by their correlation distributions.

rep_correlations = c()
tissues = unique(sapply(metabolomics_processed_datasets,function(x)x$tissue))
for(pair_name in names(norm_method2comparison_results)){
  single_metabolite_corrs = 
    norm_method2comparison_results[[pair_name]]$single_metabolite_corrs
  for(tissue in tissues){
    curr_datasets = names(single_metabolite_corrs)[
      grepl(tissue,names(single_metabolite_corrs))
    ]
    between_corrs = c()
    for(tar_dataset in curr_datasets){
      l = single_metabolite_corrs[[tar_dataset]]
      between_corrs = c(between_corrs,unname(unlist(sapply(l,diag))))
    }
    rep_correlations = rbind(rep_correlations,
          c(pair_name,tissue,mean(between_corrs,na.rm=T),sd(between_corrs,na.rm=T)))
  }
}
rep_correlations = rep_correlations[!is.na(rep_correlations[,3]),]
rep_correlations = data.frame(
  "NormMethod" = rep_correlations[,1],
  "Tissue" = rep_correlations[,2],
  "Mean" = as.numeric(rep_correlations[,3]),
  "SD" = as.numeric(rep_correlations[,4])
)
rep_correlations = rep_correlations[order(rep_correlations$Tissue),]
print(
    ggplot(rep_correlations, aes(x=Tissue, y=Mean, fill=NormMethod)) +
      geom_bar(position=position_dodge(), stat="identity", colour='black') +
      geom_errorbar(aes(ymin=Mean-SD, ymax=Mean+SD),na.rm=T, 
                   width=.2,position=position_dodge(.9))
)

Differential analysis results

tissues
[1] "plasma"        "gastrocnemius" "liver"         "white_adipose" "heart"        

Meta-analysis results

    names(pvals_untar)[significant_in=="Untargeted"]
 [1] "alanine,plasma,0"                     "histidine,plasma,0"                  
 [3] "Car(10:0),plasma,0"                   "Car(14:2),plasma,0"                  
 [5] "Car(16:0),plasma,0"                   "Car(4:0-OH),plasma,0"                
 [7] "Car(5:1),plasma,0"                    "Car(8:0),plasma,0"                   
 [9] "Car(10:1),plasma,0"                   "Car(10:2),plasma,0"                  
[11] "Car(4:0),plasma,0"                    "carnitine,plasma,0"                  
[13] "glutamine,plasma,0"                   "lysine,plasma,0"                     
[15] "threonine,plasma,0"                   "sphingosine-1P (C18),plasma,0"       
[17] "histidine,plasma,1"                   "Car(10:0),plasma,1"                  
[19] "Car(3:0),plasma,1"                    "Car(5:0) isomers,plasma,1"           
[21] "Car(5:1),plasma,1"                    "Car(8:0),plasma,1"                   
[23] "Car(10:1),plasma,1"                   "Car(20:0),plasma,1"                  
[25] "beta-alanine,plasma,1"                "glutamine,plasma,1"                  
[27] "tryptophan,plasma,1"                  "sphinganine (C18),plasma,1"          
[29] "sphingosine-1P (C18),plasma,1"        "cis-aconitate,plasma,1"              
[31] "arginine,plasma,4"                    "Car(10:0),plasma,4"                  
[33] "Car(12:0),plasma,4"                   "Car(12:1),plasma,4"                  
[35] "Car(14:1),plasma,4"                   "Car(14:2),plasma,4"                  
[37] "Car(20:4),plasma,4"                   "Car(3:0),plasma,4"                   
[39] "Car(5:1),plasma,4"                    "Car(8:0),plasma,4"                   
[41] "Car(10:1),plasma,4"                   "beta-alanine,plasma,4"               
[43] "taurine,plasma,4"                     "Cer(d34:1)>Cer(d18:1/16:0),plasma,4" 
[45] "Cer(d42:2)>Cer(d18:1/24:1),plasma,4"  "sphingosine-1P (C18),plasma,4"       
[47] "Car(5:1),plasma,48"                   "taurine,plasma,48"                   
[49] "Cer(d34:1)>Cer(d18:1/16:0),plasma,48" "SM(d44:2),plasma,48"                 
[51] "cis-aconitate,plasma,48"              "Car(10:0),plasma,7"                  
[53] "Car(18:0),plasma,7"                   "Car(10:2),plasma,7"                  
[55] "carnitine,plasma,7"                   "tryptophan,plasma,7"                 
[57] "histidine,plasma,24"                  "Car(5:1),plasma,24"                  
[59] "Car(10:2),plasma,24"                  "isoleucine,plasma,24"                
[61] "taurine,plasma,24"                    "tryptophan,plasma,24"                
[63] "Cer(d42:2)>Cer(d18:1/24:1),plasma,24" "Car(10:0),plasma,0.5"                
[65] "Car(20:4),plasma,0.5"                 "Car(4:0-OH),plasma,0.5"              
[67] "Car(5:1),plasma,0.5"                  "Car(6:0),plasma,0.5"                 
[69] "Car(8:0),plasma,0.5"                  "Car(10:1),plasma,0.5"                
[71] "beta-alanine,plasma,0.5"              "glutamine,plasma,0.5"                
[73] "threonine,plasma,0.5"                 "fumarate,plasma,0.5"                 
[75] "succinate,plasma,0.5"                

Comparison results: detailed analysis of the selected normalization

Spearman correlations

We examine the average correlation between the platforms (within tissues). Whenever two platforms share more than a single metabolite we plot both the average correlation between the same metabolites and between other metabolites. Adding the average correlation between platforms but with different metabolites is important as it gives some perspective to what a significant correlation is. That is, in many cases below, the average correlation may be greater than expected.

# Next examine the Spearman correlations between platforms
mean_abs<-function(x,...){return(mean(abs(x),...))}
sd_abs<-function(x,...){return(sd(abs(x),...))}
extract_diag_vs_non_diag<-function(corrs,func=mean,...){
  if(length(corrs)==1){
    return(c(same=func(corrs,...),other=NA))
  }
  same = func(diag(corrs),...)
  other = func(
    c(corrs[lower.tri(corrs,diag = F)]),...)
  return(c(same=same,other=other))
}
single_metabolite_corrs =
  norm_method2comparison_results$`log2,imp;log2,imp`$single_metabolite_corrs
for(tar_dataset in names(single_metabolite_corrs)){
  l = single_metabolite_corrs[[tar_dataset]]
  if(length(l)==0){next}
  corr_info = as.data.frame(t(sapply(l, extract_diag_vs_non_diag)))
  corr_sd = as.data.frame(t(sapply(l, extract_diag_vs_non_diag,func=sd)))
  # shorten the row names
  rownames(corr_info) = sapply(rownames(corr_info),
      function(x)paste(strsplit(x,split=",")[[1]][3:4],collapse=","))
  rownames(corr_sd) = rownames(corr_info)
  corr_info$dataset = rownames(corr_info)
  corr_sd$dataset = corr_info$dataset
  corr_info = melt(corr_info)
  corr_sd = melt(corr_sd)
  corr_info$sd = corr_sd$value
  print(
    ggplot(corr_info, aes(x=dataset, y=value, fill=variable)) +
      geom_bar(position=position_dodge(), stat="identity", colour='black') +
      geom_errorbar(aes(ymin=value-sd, ymax=value+sd),na.rm=T, 
                   width=.2,position=position_dodge(.9)) +
    ggtitle(tar_dataset) + xlab("Untargeted dataset") + ylab("Spearman") +
      labs(fill = "Pair type") + 
      theme(legend.position="top",legend.direction = "horizontal")
  )
}

Plot selected examples

Here are the results for lactate in plasma.

Example from a log2 dataset:

Example from a scaled dataset:

meta_analysis_stats = 
  norm_method2comparison_results$`imp,none;imp,none`$meta_analysis_stats
lact_res = meta_analysis_stats[
  grepl("lact",names(meta_analysis_stats),ignore.case = T) &
    grepl("plasma",names(meta_analysis_stats),ignore.case = T)
]
lact_res_hours = sapply(names(lact_res),
            function(x)as.numeric(strsplit(x,split=",")[[1]][3]))
lact_res = lact_res[order(lact_res_hours)]
for(lact_example in names(lact_res)[1:6]){
  curr_labels = gsub("plasma,","",
                     meta_analysis_stats[[lact_example]][[1]][,1])
  forest(meta_analysis_stats[[lact_example]]$re_model1,
       slab = curr_labels,
       main = lact_example,xlab = "Log fc",
       col = "blue",cex = 1.1)
}

We can now check the same analysis for liver:

lact_res = meta_analysis_stats[
  grepl("lact",names(meta_analysis_stats),ignore.case = T) &
    grepl("liver",names(meta_analysis_stats),ignore.case = T)
]
lact_res_hours = sapply(names(lact_res),
            function(x)as.numeric(strsplit(x,split=",")[[1]][3]))
lact_res = lact_res[order(lact_res_hours)]
for(lact_example in names(lact_res)[1:6]){
  curr_labels = gsub("liver_powder,","",
                     meta_analysis_stats[[lact_example]][[1]][,1])
  forest(meta_analysis_stats[[lact_example]]$re_model1,
       slab = curr_labels,
       main = lact_example,xlab = "Log fc",
       col = "blue",cex = 1.1)
}

From the plots above we take the most extreme examples and examine their forest plots.

meta_analysis_stats = 
  norm_method2comparison_results$`imp,none;imp,none`$meta_analysis_stats

# P-value for the difference between targeted and untargeted
targeted_diff_p = 
  sapply(meta_analysis_stats,function(x)x$re_model2$pval[2])

# P-values - targeted vs. untargeted
pvals_tar = sapply(meta_analysis_stats,function(x)x$re_model_tar$pval)
pvals_untar = sapply(meta_analysis_stats,function(x)x$re_model_untar$pval)
pvals_untar = unlist(pvals_untar[sapply(pvals_untar,length)>0])


agree_example = names(sample(which(pvals_tar< 1e-5 & pvals_untar < 1e-5 &
                                     targeted_diff_p > 0.1))[1])
simplify_labels_for_forest<-function(s){
  s = gsub(",untargeted","",s)
  tissue = strsplit(s,split=",")[[1]][1]
  s = gsub(paste(tissue,",",sep=""),"",s)
  return(s)
}
forest(meta_analysis_stats[[agree_example]]$re_model1,
  slab = simplify_labels_for_forest(
    meta_analysis_stats[[agree_example]][[1]][,1]),
  main = paste(agree_example,"significant in both, tar and untar agree",sep="\n"),
  xlab = "Log fc",col = "blue")

agree_p_disagree_beta = names(sample(which(pvals_tar< 1e-5 & pvals_untar < 1e-5 &
                                     targeted_diff_p < 0.001))[1])
forest(meta_analysis_stats[[agree_p_disagree_beta]]$re_model1,
  slab = simplify_labels_for_forest(
    meta_analysis_stats[[agree_p_disagree_beta]][[1]][,1]),
  main = paste(agree_p_disagree_beta,
               "significant in both, tar and untar disagree",sep="\n"),
  xlab = "Log fc",col = "blue")

disagree_example1 = names(sample(which(pvals_tar< 1e-10 & pvals_untar >0.1))[1])
forest(meta_analysis_stats[[disagree_example1]]$re_model1,
  slab = simplify_labels_for_forest(
    meta_analysis_stats[[disagree_example1]][[1]][,1]),
  main = paste(disagree_example1,
               "significant targeted, tar and untar disagree",sep="\n"),
  xlab = "Log fc",col = "blue")


disagree_example2 = names(sample(which(pvals_tar > 0.1 & pvals_untar < 1e-20))[1])
forest(meta_analysis_stats[[disagree_example2]]$re_model1,
  slab = simplify_labels_for_forest(
    meta_analysis_stats[[disagree_example2]][[1]][,1]),
  main = paste(disagree_example2,
               "significant in untargeted, tar and untar disagree",sep="\n"),
  xlab = "Log fc",col = "blue")

Targeted vs. untargeted: comparison as a prediction task

Use 5-fold cross validation for analysis within tissues. For each pair of targeted and untargeted datasets from the same tissue, we use the untargeted data as the predictive features and all metabolites in the targeted datasets as the dependent variables. The code below uses feature selection and random forests to train the predictive models.

nfolds = 5
prediction_analysis_results = list()
for(nn1 in names(metabolomics_processed_datasets)){
  nn1_tissue = strsplit(nn1,split=",")[[1]][1]
  nn1_tissue = gsub("_powder","",nn1_tissue)
  if(grepl("untargeted",nn1)){next}
  for(nn2 in names(metabolomics_processed_datasets)){
    if(nn2 == nn1){next}
    if(!grepl("untargeted",nn2)){next}
    nn2_tissue = strsplit(nn2,split=",")[[1]][1]
    nn2_tissue = gsub("_powder","",nn2_tissue)
    nn2_dataset = strsplit(nn2,split=",")[[1]][2]
    if(nn1_tissue!=nn2_tissue){next}
    print(paste("features from:",nn2))
    print(paste("labels from:",nn1))
    # get the numeric datasets and their annotation
    y = metabolomics_processed_datasets[[nn1]]$normalized_data[[1]]
    x = metabolomics_processed_datasets[[nn2]]$normalized_data[[1]]
    # align the sample sets
    bid_y = merged_dmaqc_data[colnames(y),"bid"]
    bid_x = merged_dmaqc_data[colnames(x),"bid"]    
    # step 1: merge samples from the same BID
    if(length(unique(bid_x))!=length(bid_x)){
      x = aggregate_repeated_samples(x,bid_x)
    }
    else{
      colnames(x) = bid_x
    }
    if(length(unique(bid_y))!=length(bid_y)){
      y = aggregate_repeated_samples(y,bid_y)
    }else{
      colnames(y) = bid_y
    }
    # step 2: use the shared bio ids
    shared_bids = as.character(intersect(colnames(y),colnames(x)))
    x = t(as.matrix(x[,shared_bids]))
    y = t(as.matrix(y[,shared_bids]))
    # At this point x and y are over the same BIDs, now we add the metadata
    y_meta = unique(metabolomics_processed_datasets[[nn1]]$sample_meta_parsed)
    rownames(y_meta) = y_meta$bid
    y_meta = y_meta[shared_bids,]
    
    # take the covariates (ignore distances)
    curr_cov_cols = intersect(colnames(y_meta),biospec_cols[2])
    curr_covs = data.frame(y_meta[,curr_cov_cols])
    names(curr_covs) = curr_cov_cols
    curr_covs$sex = y_meta$animal.registration.sex # add sex
    # add the covariates into x
    x = cbind(x,curr_covs)
    
    # Run the regressions
    folds = sample(rep(1:nfolds,(1+nrow(x)/nfolds)))[1:nrow(x)]
    numFeatures = min(ncol(x),2000)
    preds = c();real=c()
    for(i in 1:ncol(y)){
      if( i %% 10 == 0){print(paste("analyzing metabolite number:",i))}
      y_i = y[,1]
      i_preds = c();i_real=c()
      for(j in 1:nfolds){
        tr_x = x[folds!=j,]
        tr_yi = y_i[folds!=j]
        te_x = x[folds==j,]
        te_y = y_i[folds==j]
        # random forest
        # model = randomForest(tr_yi,x=tr_x,ntree = 20)
        # te_preds = predict(model,newdata = te_x)
        model = feature_selection_wrapper(tr_x,tr_yi,
                   coeff_of_var,randomForest,
                   topK = numFeatures,ntree=50)
        te_preds = predict(model,newdata = te_x)
        i_preds = c(i_preds,te_preds)
        i_real = c(i_real,te_y)
      }
      preds = cbind(preds,i_preds)
      real = cbind(real,i_real)
    }
    colnames(preds) = colnames(y)
    colnames(reals) = colnames(y)
    currname = paste(nn1,nn2,sep=";")
    prediction_analysis_results[[currname]] = list(
      preds = preds,real=real
    )
  }
}
save_to_bucket(prediction_analysis_results,
               file="tar_vs_untar_prediction_analysis_results.RData",
               bucket = "gs://bic_data_analysis/pass1a/metabolomics/")

We now take the predicted and real values and estimate the prediction accuracy in different ways.

prediction_analysis_results =  load_from_bucket(
  "tar_vs_untar_prediction_analysis_results.RData",
    "gs://bic_data_analysis/pass1a/metabolomics/",F)
prediction_analysis_results = prediction_analysis_results[[1]]

results_metrics = list()
for(nn in names(prediction_analysis_results)){
  preds = prediction_analysis_results[[nn]]$preds
  real = prediction_analysis_results[[nn]]$real
  tar_name = strsplit(nn,split=";")[[1]][1]
  untar_name = strsplit(nn,split=";")[[1]][2]
  y = metabolomics_processed_datasets[[tar_name]]$normalized_data[[1]]
  colnames(preds) = rownames(y)
  colnames(real) = rownames(y)
  tar_name = simplify_metab_dataset_name(tar_name)
  untar_name = simplify_metab_dataset_name(untar_name)
  currtissue = strsplit(tar_name,split=",")[[1]][1]
  tar_name = gsub(paste(currtissue,",",sep=""),"",tar_name)
  untar_name = gsub(paste(currtissue,",",sep=""),"",untar_name)
  if(! currtissue %in% names(results_metrics)){
    results_metrics[[currtissue]] = list()
  }
  if(! tar_name %in% names(results_metrics[[currtissue]])){
    results_metrics[[currtissue]][[tar_name]] = list()
  }
  
  rhos = format(diag(cor(preds,real,method="spearman")),digits=3)
  rhos = as.numeric(rhos)
  SEs = colSums((preds-real)^2)
  MSEs = SEs / nrow(preds)
  RMSE = sqrt(MSEs)
  rMSE = MSEs / apply(y,1,var)
  CoVs = apply(y,1,sd) / apply(y,1,mean)
  discCoVs = cut(CoVs,breaks = 2,ordered_result = T)
  
  results_metrics[[currtissue]][[tar_name]][[untar_name]] = data.frame(
    rhos,MSEs,RMSE,rMSE,CoVs,discCoVs
  )
}

We now present a few summary plots.

for(tissue in names(results_metrics)){
  for(tar in names(results_metrics[[tissue]])){
    l = results_metrics[[tissue]][[tar]]
    rho_vs_cv = c()
    for(untar in names(l)){
      m = l[[untar]][,c("rhos","discCoVs")] # take the current matrix
      m = cbind(rep(untar,nrow(m)),m)
      m$discCoVs = as.numeric(m$discCoVs)
      rho_vs_cv = rbind(rho_vs_cv,m)
    }
    colnames(rho_vs_cv)[1] = "dataset"
    boxplot(rhos~discCoVs:dataset,data=rho_vs_cv,las=2,
            ylab="Spearman",xlab = "",ylim=c(0,1),
            main = paste(tissue,tar,sep=","))
  }
}

As additional references, we train below additional models. First, we check the prediction of naive models that use technical and clinical covariates only. Second, we use multi-task regression and deep learning models.

cov_prediction_analysis_results = list()
for(nn1 in names(metabolomics_processed_datasets)){
  nn1_tissue = strsplit(nn1,split=",")[[1]][1]
  nn1_tissue = gsub("_powder","",nn1_tissue)
  if(grepl("untargeted",nn1)){next}
  print(nn1)
  y = metabolomics_processed_datasets[[nn1]]$normalized_data[[1]]
  y_vials = colnames(y)
  bid_y = merged_dmaqc_data[colnames(y),"bid"]
  colnames(y) = bid_y
  y = t(as.matrix(y))
  if(ncol(y)>1000){next}
  cov_cols = c("animal.registration.sex",
             "acute.test.weight",
             "acute.test.distance",
             "animal.key.timepoint")
  covs = merged_dmaqc_data[y_vials,cov_cols]
  x = covs
  
  # Run the regressions
  folds = sample(rep(1:nfolds,(1+nrow(x)/nfolds)))[1:nrow(x)]
  numFeatures = min(ncol(x),2000)
  preds = c();real=c()
  for(i in 1:ncol(y)){
    y_i = y[,1]
    i_preds = c();i_real=c()
    for(j in 1:nfolds){
      print(j)
      tr_x = x[folds!=j,]
      tr_yi = y_i[folds!=j]
      te_x = x[folds==j,]
      te_y = y_i[folds==j]
      # random forest
      model = randomForest(tr_yi,x=tr_x,ntree = 20)
      te_preds = predict(model,newdata = te_x)
      i_preds = c(i_preds,te_preds)
      i_real = c(i_real,te_y)
    }
    preds = cbind(preds,i_preds)
    real = cbind(real,i_real)
  }
  cov_prediction_analysis_results[[nn1]] = list(
      preds = preds,real=real
    )
}

# preds = c();real=c()
# for(j in 1:nfolds){
#   tr_x = x[folds!=j,]
#   tr_y = y[folds!=j,]
#   te_x = x[folds==j,]
#   te_y = y[folds==j,]
#   model = MTL_wrapper(tr_x,tr_y,type="Regression", Regularization="L21")
#   te_preds = predict(model,te_x)
#   real = rbind(real,te_y)
#   preds = rbind(preds,te_preds)
# }
# diag(cor(preds,real))

# Using PLS regression
# library(pls)
# pls_model = plsr(y~x,ncomp = 5,validation="LOO")
# eval = MSEP(pls_model)
# 
# y_pca = prcomp(y)
# plot(y_pca)
# explained_var = y_pca$sdev^2/sum(y_pca$sdev^2)
# y_pca_matrix = y_pca$x[,1:10]
# 
# # regress out sex, weight
# 
# get_explained_variance_using_PCA(x,y)
# x = apply(x,2,regress_out,covs=covs)
# y = apply(y,2,regress_out,covs=covs)
# get_explained_variance_using_PCA(x,y)
LS0tCnRpdGxlOiAiQklDIG1ldGFib2xvbWljcyBkYXRhIGFuYWx5c2lzIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6CiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwotLS0KCkluIHRoaXMgZG9jdW1lbnQgd2UgcHJlc2VudCB0aGUgam9pbnQgYW5hbHlzaXMgb2YgdGhlIFBBU1MxQSBtZXRhYm9sb21pY3MgZGF0YXNldHMuCgojIExvYWQgYWxsIGRhdGFzZXRzCgpMb2FkIHRoZSBkYXRhIGZyb20gdGhlIGNsb3VkLCBpbmNsdWRpbmc6IHBoZW5vdHlwaWMgZGF0YSwgbWV0YWJvbG9taWMgZGF0YXNldHMsIGFuZCBtZXRhYm9sb21pY3MgZGljdGlvbmFyeS4KCmBgYHtyLHJlc3VsdHM9J2hpZGUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0Kc291cmNlKCJ+L0Rlc2t0b3AvcmVwb3MvbW90cnBhYy1iaWMtbm9ybS1xYy90b29scy9zdXBlcnZpc2VkX25vcm1hbGl6YXRpb25fZnVuY3Rpb25zLlIiKQpzb3VyY2UoIn4vRGVza3RvcC9yZXBvcy9tb3RycGFjLWJpYy1ub3JtLXFjL3Rvb2xzL3Vuc3VwZXJ2aXNlZF9ub3JtYWxpemF0aW9uX2Z1bmN0aW9ucy5SIikKc291cmNlKCJ+L0Rlc2t0b3AvcmVwb3MvbW90cnBhYy1iaWMtbm9ybS1xYy90b29scy9nY3BfZnVuY3Rpb25zLlIiKQpzb3VyY2UoIn4vRGVza3RvcC9yZXBvcy9tb3RycGFjLWJpYy1ub3JtLXFjL3Rvb2xzL2Fzc29jaWF0aW9uX2FuYWx5c2lzX21ldGhvZHMuUiIpCnNvdXJjZSgifi9EZXNrdG9wL3JlcG9zL21vdHJwYWMtYmljLW5vcm0tcWMvdG9vbHMvZGF0YV9hdXhfZnVuY3Rpb25zLlIiKQpzb3VyY2UoIn4vRGVza3RvcC9yZXBvcy9tb3RycGFjL3Rvb2xzL3ByZWRpY3Rpb25fbWxfdG9vbHMuUiIpCmxpYnJhcnkocmFuZG9tRm9yZXN0KSAjIGZvciBjbGFzc2lmaWNhdGlvbiB0ZXN0cwoKIyBMb2FkIHRoZSBkbWFxYyBkYXRhCm1lcmdlZF9kbWFxY19kYXRhID0gIGxvYWRfZnJvbV9idWNrZXQoIm1lcmdlZF9kbWFxY19kYXRhMjAxOS0xMC0xNS5SRGF0YSIsCiAgICAiZ3M6Ly9iaWNfZGF0YV9hbmFseXNpcy9wYXNzMWEvcGhlbm9fZG1hcWMvIixGKQptZXJnZWRfZG1hcWNfZGF0YSA9IG1lcmdlZF9kbWFxY19kYXRhW1sxXV0Kcm93bmFtZXMobWVyZ2VkX2RtYXFjX2RhdGEpID0gYXMuY2hhcmFjdGVyKG1lcmdlZF9kbWFxY19kYXRhJHZpYWxfbGFiZWwpCiMgZGVmaW5lIHRoZSB0aXNzdWUgdmFyaWFibGUKbWVyZ2VkX2RtYXFjX2RhdGEkdGlzc3VlID0gbWVyZ2VkX2RtYXFjX2RhdGEkc2FtcGxldHlwZWRlc2NyaXB0aW9uCiMgZGVmaW5lIHRoZSB0aW1lIHRvIGZyZWV6ZSB2YXJpYWJsZQptZXJnZWRfZG1hcWNfZGF0YSR0aW1lX3RvX2ZyZWV6ZSA9IG1lcmdlZF9kbWFxY19kYXRhJGNhbGN1bGF0ZWQudmFyaWFibGVzLnRpbWVfZGVhdGhfdG9fY29sbGVjdF9taW4gKyAKICBtZXJnZWRfZG1hcWNfZGF0YSRjYWxjdWxhdGVkLnZhcmlhYmxlcy50aW1lX2NvbGxlY3RfdG9fZnJlZXplX21pbgoKIyBjb2wgdGltZSB2cy4gY29udHJvbAojIGRmID0gZGF0YS5mcmFtZSgKIyAgIGJpZCA9IG1lcmdlZF9kbWFxY19kYXRhJGJpZCwKIyAgIGVkdGFfY29sX3RpbWUgPSBtZXJnZWRfZG1hcWNfZGF0YSRjYWxjdWxhdGVkLnZhcmlhYmxlcy5lZHRhX2NvbGxfdGltZSwKIyAgIHRpbWVfdG9fZnJlZXplID0gbWVyZ2VkX2RtYXFjX2RhdGEkdGltZV90b19mcmVlemUsCiMgICBpc19jb250cm9sID0gbWVyZ2VkX2RtYXFjX2RhdGEkYW5pbWFsLmtleS5pc19jb250cm9sLAojICAgdHAgPSBtZXJnZWRfZG1hcWNfZGF0YSRhbmltYWwua2V5LnRpbWVwb2ludCwKIyAgIHRpc3N1ZSA9IG1lcmdlZF9kbWFxY19kYXRhJHNwZWNpbWVuLnByb2Nlc3Npbmcuc2FtcGxldHlwZWRlc2NyaXB0aW9uCiMgKQojIGRmID0gdW5pcXVlKGRmKQojIGJveHBsb3QoZWR0YV9jb2xfdGltZS8zNjAwIH4gaXNfY29udHJvbCxkZikKIyBib3hwbG90KGVkdGFfY29sX3RpbWUvMzYwMCAtIHRwIH4gaXNfY29udHJvbCxkZikKIyB3aWxjb3gudGVzdChlZHRhX2NvbF90aW1lLzM2MDAgfiBpc19jb250cm9sLGRmKQoKIyBibG9vZCBmcmVlemUgdGltZXMKYmxvb2Rfc2FtcGxlcyA9IAogIG1lcmdlZF9kbWFxY19kYXRhJHNwZWNpbWVuLnByb2Nlc3Npbmcuc2FtcGxldHlwZWRlc2NyaXB0aW9uID09CiAgIkVEVEEgUGxhc21hIgpibG9vZF9mcmVlemVfdGltZSA9IAogIGFzLmRpZmZ0aW1lKG1lcmdlZF9kbWFxY19kYXRhJHNwZWNpbWVuLnByb2Nlc3NpbmcudF9mcmVlemUsdW5pdHMgPSAibWlucyIpIC0KICBhcy5kaWZmdGltZShtZXJnZWRfZG1hcWNfZGF0YSRzcGVjaW1lbi5wcm9jZXNzaW5nLnRfZWR0YXNwaW4sdW5pdHM9Im1pbnMiKQpibG9vZF9mcmVlemVfdGltZSA9IGFzLm51bWVyaWMoYmxvb2RfZnJlZXplX3RpbWUpCnRpbWVfdG9fZnJlZXplID0gbWVyZ2VkX2RtYXFjX2RhdGEkdGltZV90b19mcmVlemVbYmxvb2Rfc2FtcGxlc10gPSAKICBibG9vZF9mcmVlemVfdGltZVtibG9vZF9zYW1wbGVzXQoKIyBMb2FkIG91ciBwYXJzZWQgbWV0YWJvbG9taWNzIGRhdGFzZXRzCm1ldGFib2xvbWljc19idWNrZXRfb2JqID0gbG9hZF9mcm9tX2J1Y2tldCgKICBmaWxlID0gIm1ldGFib2xvbWljc19wYXJzZWRfZGF0YXNldHNfcGFzczFhX2V4dGVybmFsMS5SRGF0YSIsCiAgYnVja2V0ID0gImdzOi8vYmljX2RhdGFfYW5hbHlzaXMvcGFzczFhL21ldGFib2xvbWljcy8iKQptZXRhYm9sb21pY3NfcGFyc2VkX2RhdGFzZXRzID0gbWV0YWJvbG9taWNzX2J1Y2tldF9vYmokbWV0YWJvbG9taWNzX3BhcnNlZF9kYXRhc2V0cwoKYGBgCkRlZmluZSB0aGUgdmFyaWFibGVzIHRvIGJlIGFkanVzdGVkIGZvcjoKCmBgYHtyfQpiaW9zcGVjX2NvbHMgPSBjKAogICJhY3V0ZS50ZXN0LmRpc3RhbmNlIiwKICAiY2FsY3VsYXRlZC52YXJpYWJsZXMudGltZV90b19mcmVlemUiLAogICMgImNhbGN1bGF0ZWQudmFyaWFibGVzLmVkdGFfY29sbF90aW1lIiwgIyBubyBuZWVkIC0gc2VlIGNvZGUgYWJvdmUgZm9yIGJsb29kCiAgImJpZCIgIyByZXF1aXJlZCBmb3IgbWF0Y2hpbmcgZGF0YXNldHMKICApCmRpZmZlcmVudGlhbF9hbmFseXNpc19jb2xzID0gYygKICAiYW5pbWFsLnJlZ2lzdHJhdGlvbi5zZXgiLAogICJhbmltYWwua2V5LnRpbWVwb2ludCIsCiAgImFuaW1hbC5rZXkuaXNfY29udHJvbCIKKQpwaXBlbGluZV9xY19jb2xzID0gYygic2FtcGxlX29yZGVyIikKYGBgCgojIExvZy10cmFuc2Zvcm06IGVmZmVjdCBvbiB2YXJpYW5jZQoKU29tZSBzaXRlcyBkbyBub3QgdXNlIHRoZSBsb2cgdHJhbnNmb3JtYXRpb24gb24gdGhlaXIgZGF0YXNldC4gSW4gdGhpcyBzZWN0aW9uIHdlIHBsb3QgdGhlIGNvZWZmaWNpZW50IG9mIHZhcmlhdGlvbiBhcyBhIGZ1bmN0aW9uIG9mIHRoZSBtZWFuIGluc3RlbnNpdHkuIFdlIHRha2UgYSBzaW5nbGUgZGF0YXNldCBhcyBhbiBleGFtcGxlIHRvIHNob3cgaG93IGxvZy10cmFuc2Zvcm1lZCBkYXRhIGhhdmUgcmVkdWNlZCBkZXBlbmRlbmN5IGFuZCBzbW9vdGhlciBwbG90cy4KCkFzIGFuIGFkZGl0aW9uYWwgYW5hbHlzaXMgd2UgYWxzbyBwbG90IHRoZSBudW1iZXIgb2YgbWlzc2luZyB2YWx1ZXMgcGVyIG1ldGFib2xpdGUgYXMgYSBmdW5jdGlvbiBvZiBpdHMgbWVhbiBpbnRlbnNpdHkuIFdlIHNob3cgdGhhdCB3aGlsZSB0aGVyZSBpcyBoaWdoIGNvcnJlbGF0aW9uIHNvbWUgbWlzc2luZyB2YWx1ZXMgYXBwZWFyIGluIGZhaXJlbHkgaGlnaCBpbnRlbnNpdGllcy4gVGhpcyBpcyBpbXBvcnRhbnQgZm9yIGltcHV0YXRpb24gYXMgc29tZSBzaXRlcyB1c2Ugc29tZSBmaXhlZCBsb3cgdmFsdWUgaW5zdGVhZCBvZiBrbm4gaW1wdXRhdGlvbi4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KCiMgUGxvdCBjdiB2cyBtZWFucwpsaWJyYXJ5KGdwbG90cykKZCA9IG1ldGFib2xvbWljc19wYXJzZWRfZGF0YXNldHNbWyJ3aGl0ZV9hZGlwb3NlX3Bvd2RlcixtZXRhYl91X2hpbGljcG9zLHVubmFtZWQiXV0KZHggPSBkJHNhbXBsZV9kYXRhCkNvVjwtZnVuY3Rpb24oeCl7cmV0dXJuKHNkKHgsbmEucm0gPSBUKS9tZWFuKHgsbmEucm09VCkpfQpkbWVhbnMgPSBhcHBseShkeCwxLG1lYW4sbmEucm09VCkKQ29WcyA9IGFwcGx5KGR4LDEsQ29WKQppbmRzID0gIWlzLm5hKENvVnMpCmRmID0gZGF0YS5mcmFtZShNZWFuX2ludGVuc2l0eSA9IGRtZWFuc1tpbmRzXSxDb1YgPSBDb1ZzW2luZHNdKQpwbG90KENvVn5NZWFuX2ludGVuc2l0eSxkZixjZXg9MC41LHBjaD0yMCxtYWluPSJSYXcgZGF0YSIpCmxpbmVzKGxvd2VzcyhDb1Z+TWVhbl9pbnRlbnNpdHksZGYpLGx0eT0yLGx3ZD0yLGNvbD0iYmx1ZSIpCgojIFJlcGVhdCBhZnRlciBsb2cyCmR4ID0gbG9nKDErZCRzYW1wbGVfZGF0YSxiYXNlPTIpCmRtZWFucyA9IGFwcGx5KGR4LDEsbWVhbixuYS5ybT1UKQpDb1ZzID0gYXBwbHkoZHgsMSxDb1YpCmluZHMgPSAhaXMubmEoQ29WcykKZGYgPSBkYXRhLmZyYW1lKE1lYW5faW50ZW5zaXR5ID0gZG1lYW5zW2luZHNdLENvViA9IENvVnNbaW5kc10pCnBsb3QoQ29Wfk1lYW5faW50ZW5zaXR5LGRmLGNleD0wLjUscGNoPTIwLG1haW49IkxvZzIgZGF0YSIpCmxpbmVzKGxvd2VzcyhDb1Z+TWVhbl9pbnRlbnNpdHksZGYpLGx0eT0yLGx3ZD0yLGNvbD0iYmx1ZSIpCgojIFBsb3QgbnVtYmVyIG9mIE5BcyB2cyBpbnRlbnNpdHkgbWVhbgpkeCA9IGxvZygxK2Qkc2FtcGxlX2RhdGEsYmFzZT0yKQpkbWVhbnMgPSBhcHBseShkeCwxLG1lYW4sbmEucm09VCkKbnVtX25hcyA9IHJvd1N1bXMoaXMubmEoZHgpKQpkZiA9IGRhdGEuZnJhbWUoTnVtX05BcyA9IG51bV9uYXNbaW5kc10sTWVhbl9pbnRlbnNpdHkgPSBkbWVhbnNbaW5kc10pCnJobyA9IGNvcihkZiROdW1fTkFzLGRmJE1lYW5faW50ZW5zaXR5LG1ldGhvZD0ic3BlYXJtYW4iKQpyaG8gPSBmb3JtYXQocmhvLGRpZ2l0cz0yKQpwbG90KE51bV9OQXN+TWVhbl9pbnRlbnNpdHksZGYsY2V4PTAuNSxwY2g9MjAsCiAgICAgbWFpbj1wYXN0ZSgiU3BlYXJtYW46IixyaG8pKQoKCmBgYAoKIyBMb2FkIHRoZSBwcmVwcm9jZXNzZWQgYW5kIG5vcm1hbGl6ZWQgZGF0YQoKYGBge3J9Cm1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHMgPSBsb2FkX2Zyb21fYnVja2V0KAogIGZpbGU9Im1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHMxMTE4MjAxOS5SRGF0YSIsCiAgYnVja2V0ID0gImdzOi8vYmljX2RhdGFfYW5hbHlzaXMvcGFzczFhL21ldGFib2xvbWljcy8iCilbWzFdXQoKIyBSZWR1Y2UgdGhlIG1ldGFkYXRhIHRvIHRoZSBzZWxlY3RlZCBjb2x1bW5zCmZvcihjdXJybmFtZSBpbiBuYW1lcyhtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzKSl7CiAgY3Vycl9kYXRhID0gbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbY3Vycm5hbWVdXSRub3JtYWxpemVkX2RhdGFbWzFdXQogICMgb3JnYW5pemUgdGhlIG1ldGFkYXRhCiAgY3Vycl9tZXRhID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoY3Vycl9kYXRhKSwKICAgICAgICB1bmlvbihiaW9zcGVjX2NvbHMsZGlmZmVyZW50aWFsX2FuYWx5c2lzX2NvbHMpXQogICMgcmVtb3ZlIG1ldGFkYXRhIHZhcmlhYmxlcyB3aXRoIHRvbyBtYW55IE5BcwogIG5hX2NvdW50cyA9IGFwcGx5KGlzLm5hKGN1cnJfbWV0YSksMixzdW0pCiAgY3Vycl9tZXRhID0gY3Vycl9tZXRhWyxuYV9jb3VudHMvbnJvdyhjdXJyX21ldGEpIDwgMC4xXQogIG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW2N1cnJuYW1lXV0kc2FtcGxlX21ldGFfcGFyc2VkID0gY3Vycl9tZXRhCn0KYGBgCgojIExvZy10cmFuc2Zvcm06IGVmZmVjdCBvbiBkaWZmZXJlbnRpYWwgYW5hbHlzaXMgaW4gdGFyZ2V0ZWQgZGF0YQoKVW50YXJnZXRlZCBkYXRhIGFyZSB0eXBpY2FsbHkgbG9nLXRyYW5zZm9ybWVkIGFuZCBhbmFseXplZCB1c2luZyBsaW5lYXIgbW9kZWxzLiBPbiB0aGUgb3RoZXIgaGFuZCwgY29uY2VudHJhdGlvbiBkYXRhIGFyZSBzb21ldGltZXMgYW5hbHl6ZWQgd2l0aCB0aGUgc2FtZSB0eXBlIG9mIG1vZGVscyBidXQgdXNpbmcgdGhlIG9yaWdpbmFsIGRhdGEuIFRoaXMgcmFpc2VzIGEgcHJvYmxlbSBpZiB3ZSB3aXNoIHRvIGNvbXBhcmUgZXhhY3Qgc3RhdGlzdGljcyBmcm9tIHRoZXNlIGRhdGEuIEluIHRoaXMgc2VjdGlvbiB3ZSBwZXJmb3JtIHJlc2lkdWFsIGFuYWx5c2lzIGZvciBzaW5nbGUgbWV0YWJvbGl0ZXMuIE91ciBnb2FsIGlzIHRvIGlkZW50aWZ5IGlmIGNvbmNlbnRyYXRpb24gZGF0YSBiZWhhdmVzICJub3JtYWxseSIgd2hlbiBub3QgbG9nLXRyYW5zZm9ybWVkLiBUaGUgYW5hbHlzaXMgYmVsb3cgZXhhbWluZXMgdGhlIHJlc2lkdWFscyBvZiB0aGUgZGF0YSBhZnRlciBmaXR0aW5nIGxpbmVhciBtb2RlbHMgZm9yIGVhY2ggbWV0YWJvbGl0ZSwgYWRqdXN0aW5nIGZvciBmcmVlemUgdGltZSBhbmQgc2V4LiBXZSB0aGVuIGNvbXBhcmUgdGhlIHJlc3VsdHMgd2l0aCBhbmQgd2l0aG91dCB0aGUgbG9nLXRyYW5zZm9ybWF0aW9uLCBjb3VudGluZyB0aGUgbnVtYmVyIG9mIG1ldGFib2xpdGVzIHdpdGggYSBzaWduaWZpY2FudCBldmlkZW5jZSBmb3Igbm9uLW5vcm1hbGx5IGRpc3RyaWJ1dGVkIHJlc2lkdWFscy4gCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CiMgY2hlY2sgZm9yIG5vcm1hbGl0eSB1c2luZyB0aGUgS29sbW9nb3Jvdi1TbWlybm92IHRlc3QKaXNfbm9ybWFsX3Rlc3Q8LWZ1bmN0aW9uKHYpewogIGlmKHNkKHYsbmEucm0gPSBUKT09MCl7cmV0dXJuKDApfQogIHRyeSh7cmV0dXJuKHNoYXBpcm8udGVzdCh2KSRwLnZhbHVlKX0pCiAgIyBUaGUgU2hhcGlybyB0ZXN0IG1heSBmYWlsIGlmIHRoZSBzZCBvZiB2IGlzIHplcm8KICByZXR1cm4oa3MudGVzdCh2LCJwbm9ybSIsbWVhbih2LG5hLnJtPVQpLHNkKHYsbmEucm0gPSBUKSkkcC52YWx1ZSkKfQojIGdvIG92ZXIgdGhlIG5hbWVkIGRhdGFzZXRzLCBnZXQgYSBsb2dnZWQgYW5kIGFuIHVubG9nZ2VkIHZlcnNpb24gb2YKIyB0aGUgZGF0YSwgdXNlIHRoZXNlIGFzIGlucHV0cyBmb3IgdGhlIHJlZ3Jlc3Npb24KcmVzaWR1YWxfYW5hbHlzaXNfcmVzdWx0cyA9IGxpc3QoKQpmb3Iobm4xIGluIG5hbWVzKG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHMpKXsKICBpZighbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kaXNfdGFyZ2V0ZWQpe25leHR9CiAgeF9sb2cgPSBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjFdXSRub3JtYWxpemVkX2RhdGFbWzFdXQogIHhfdW5sb2cgPSAyXnhfbG9nCiAgCiAgIyB0YWtlIHRoZSBjb3ZhcmlhdGVzLCBpZ25vcmUgZGlzdGFuY2VzCiAgeF9tZXRhID0gdW5pcXVlKG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMV1dJHNhbXBsZV9tZXRhX3BhcnNlZCkKICBjdXJyX2NvdnMgPSB4X21ldGFbLGludGVyc2VjdChjb2xuYW1lcyh4X21ldGEpLGJpb3NwZWNfY29sc1syXSldCiAgY3Vycl9jb3ZzID0gZGF0YS5mcmFtZShjdXJyX2NvdnMsCiAgICAgICAgICAgc2V4PXhfbWV0YSRhbmltYWwucmVnaXN0cmF0aW9uLnNleCkKICAKICAjIGdldCB0aGUgbG0gb2JqZWN0cwogIGN1cnJfbW9kZWxzID0gbGlzdCgpCiAgZm9yKHRwIGluIHVuaXF1ZSh4X21ldGEkYW5pbWFsLmtleS50aW1lcG9pbnQpKXsKICAgICAgcmVzX2xvZyA9IGFwcGx5KAogICAgICAgIHhfbG9nLDEsCiAgICAgICAgcGFzczFhX3NpbXBsZV9kaWZmZXJlbnRpYWxfYWJ1bmRhbmNlLAogICAgICAgIHRwcyA9IHhfbWV0YSRhbmltYWwua2V5LnRpbWVwb2ludCx0cD10cCwKICAgICAgICBpc19jb250cm9sID0geF9tZXRhJGFuaW1hbC5rZXkuaXNfY29udHJvbCwKICAgICAgICBjb3ZzID0gY3Vycl9jb3ZzLHJldHVybl9tb2RlbD1UCiAgICAgICkKICAgICAgcmVzX3VubG9nID0gYXBwbHkoCiAgICAgICAgeF91bmxvZywxLAogICAgICAgIHBhc3MxYV9zaW1wbGVfZGlmZmVyZW50aWFsX2FidW5kYW5jZSwKICAgICAgICB0cHMgPSB4X21ldGEkYW5pbWFsLmtleS50aW1lcG9pbnQsdHA9dHAsCiAgICAgICAgaXNfY29udHJvbCA9IHhfbWV0YSRhbmltYWwua2V5LmlzX2NvbnRyb2wsCiAgICAgICAgY292cyA9IGN1cnJfY292cyxyZXR1cm5fbW9kZWw9VAogICAgICApCiAgICAgIGlzX25vcm0gPSBjYmluZCgKICAgICAgICBzYXBwbHkocmVzX2xvZyxmdW5jdGlvbih4KWlzX25vcm1hbF90ZXN0KHJlc2lkdWFscyh4KSkpLAogICAgICAgIHNhcHBseShyZXNfdW5sb2csZnVuY3Rpb24oeClpc19ub3JtYWxfdGVzdChyZXNpZHVhbHMoeCkpKQogICAgICApCiAgICAgIGNvbG5hbWVzKGlzX25vcm0pID0gYygibG9nIiwibm90IGxvZyIpCiAgICAgIGN1cnJfbW9kZWxzW1thcy5jaGFyYWN0ZXIodHApXV0gPSBpc19ub3JtCiAgICAgIAogICAgICAjICMgdGVzdCBhIHNwZWNpZmljIG1vZGVsCiAgICAgICMgaW5kID0gMjQ2CiAgICAgICMgcGxvdCh4X3VubG9nW2luZCxdLHhfbG9nW2luZCxdKQogICAgICAjIAogICAgICAjIHJlc2lkc19sb2cgPSByc3RhbmRhcmQocmVzX2xvZ1tbaW5kXV0pCiAgICAgICMgZml0X2xvZyA9IHJlc19sb2dbW2luZF1dJGZpdHRlZC52YWx1ZXMKICAgICAgIyByZXNpZHNfdW5sb2cgPSByc3RhbmRhcmQocmVzX3VubG9nW1tpbmRdXSkKICAgICAgIyBmaXRfdW5sb2cgPSByZXNfdW5sb2dbW2luZF1dJGZpdHRlZC52YWx1ZXMKICAgICAgIyAKICAgICAgIyBwbG90KGZpdF9sb2cscmVzaWRzX2xvZykKICAgICAgIyBwbG90KGZpdF91bmxvZyxyZXNpZHNfdW5sb2cpCiAgfQogIHJlc2lkdWFsX2FuYWx5c2lzX3Jlc3VsdHNbW25uMV1dID0gY3Vycl9tb2RlbHMKfQoKIyBJcyB0aGVyZSBhIHNpZ25pZmljYW50IGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgdHdvIG9wdGlvbnM/CmxvZ192c191bmxvZ19zdW1tX21hdCA9IHNhcHBseShyZXNpZHVhbF9hbmFseXNpc19yZXN1bHRzLAogICAgZnVuY3Rpb24oeClzYXBwbHkoeCwKICAgICAgICBmdW5jdGlvbih5KQogICAgICAgICAgd2lsY294LnRlc3QoeVssMV0seVssMl0scGFpcmVkID0gVCxhbHRlcm5hdGl2ZSA9ICJnIikkcC52YWx1ZSkpCgojIENvdW50IHRoZSBudW1iZXIgb2Ygbm9uLW5vcm1hbCBtZXRhYm9saXRlcwpudW1fbm9ubm9ybWFsX2xvZyA9IHNhcHBseShyZXNpZHVhbF9hbmFseXNpc19yZXN1bHRzLAogICAgZnVuY3Rpb24oeClzYXBwbHkoeCwKICAgICAgICBmdW5jdGlvbih5KXN1bSh5WywxXTwwLjA1KSkpCm51bV9ub25ub3JtYWxfbG9nID0gCiAgbnVtX25vbm5vcm1hbF9sb2dbLG9yZGVyKGNvbG5hbWVzKG51bV9ub25ub3JtYWxfbG9nKSldCm51bV9ub25ub3JtYWxfdW5sb2cgPSBzYXBwbHkocmVzaWR1YWxfYW5hbHlzaXNfcmVzdWx0cywKICAgIGZ1bmN0aW9uKHgpc2FwcGx5KHgsCiAgICAgICAgZnVuY3Rpb24oeSlzdW0oeVssMl08MC4wNSkpKQpudW1fbm9ubm9ybWFsX3VubG9nID0gCiAgbnVtX25vbm5vcm1hbF91bmxvZ1ssb3JkZXIoY29sbmFtZXMobnVtX25vbm5vcm1hbF91bmxvZykpXQoKbGlicmFyeShjb3JycGxvdCkKcGFyKG1hciA9IGMoNSw1LDUsMTApKQpub3JtZGlmZnMgPSB0KG51bV9ub25ub3JtYWxfbG9nKS0gdChudW1fbm9ubm9ybWFsX3VubG9nKQpjb3JycGxvdChub3JtZGlmZnMsaXMuY29yciA9IEYsdGwuY2V4ID0gMC43KQpgYGAKCgojIFRyYWdldGVkIHZzLiBVbnRhcmdldGVkOiBzaW5nbGUgbWV0YWJvbGl0ZSBjb21wYXJpc29uCgojIyBDb21wdXRlIHN0YXRpc3RpY3MgZm9yIGVhY2ggZGF0YXNldAoKQ29tcGFyZSBvdmVybGFwcywgZWZmZWN0IHNpemVzLCBhbmQgY29ycmVsYXRpb25zIHdpdGhpbiB0aXNzdWVzLiBDb21wYXJlIHRhcmdldGVkLXVudGFyZ2V0ZWQgcGFpcnMgb25seS4gRm9yIGRpZmZlcmVudGlhbCBhbmFseXNpcyB3ZSB1c2UgdGhlIHNhbWUgbW9kZWwgYXMgaW4gdGhlIGFuYWx5c2lzIGFib3ZlLgoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQoKIyBUcmFuc2Zvcm0gdGhlIGRhdGEgbWF0cml4IHRvIGhhdmUgY29tcG91bmQgbmFtZXMgYXMgcm93IG5hbWVzLgojIFRoaXMgcmVxdWlyZXMgcmVtb3Zpbmcgcm93cyB3aXRob3V0IG5hbWVzIGFuZCBjaGFuZ2luZyB0aGUgcm93bmFtZXMgb2YKIyB0aGUgaW5wdXQgbWF0cml4IHguCiMgQWxzbywgZG8gbm90IGFzc3VtZSB0aGF0IHRoZSByb3cgYW5ub3RhdGlvbiBvcmRlIGZpdHMgdGhlIGRhdGEgbmVjZXNzYXJpbHkKZXh0cmFjdF9ieV9jb21wX25hbWVfZnJvbV9yb3dfYW5ub3Q8LWZ1bmN0aW9uKHgscm93X2Fubm90X3gpewogICMgZ2V0IHRoZSBjb2x1bW4gdGhhdCBoYXMgdGhlIHJvdyBuYW1lcyBvZiB4IG9yIGF0IGxlYXN0CiAgIyBoYXZlIHRoZSBncmVhdGVzdCBpbnRlcnNlY3Rpb24gd2l0aCBpdAogIGludF9zaXplcyA9IGFwcGx5KHJvd19hbm5vdF94LDIsZnVuY3Rpb24oeCx5KWxlbmd0aChpbnRlcnNlY3QoeCx5KSkseT1yb3duYW1lcyh4KSkKICBpbmQgPSB3aGljaChpbnRfc2l6ZXM9PW1heChpbnRfc2l6ZXMsbmEucm0gPSBUKSlbMV0KICAjIHVwZGF0ZSB0aGUgYW5ub3RhdGlvbiB0YWJsZSB0byBoYXZlIG9ubHkgcm93cyB0aGF0IGhhdmUgCiAgIyBhIHJvdyBpbiB4IGFuZCB0aGVuIHVwZGF0ZSB0aGUgcm93bmFtZXMgdG8gYmUgZnJvbSB0aGUgCiAgIyBzZWxlY3RlZCBjb2x1bW4KICByb3dfYW5ub3RfeCA9IHJvd19hbm5vdF94W2lzLmVsZW1lbnQocm93X2Fubm90X3hbLGluZF0sc2V0PXJvd25hbWVzKHgpKSxdCiAgcm93bmFtZXMocm93X2Fubm90X3gpID0gcm93X2Fubm90X3hbLGluZF0KICAjIHdlIGNhbiBub3cgaW50ZXJzZWN0IHggYW5kIHRoZSBhbm5vdGF0aW9uIHVzaW5nIHRoZWlyIHJvdyBuYW1lcwogICMgYW5kIHVwZGF0ZSB4IGFjY29yZGluZ2x5CiAgc2hhcmVkID0gaW50ZXJzZWN0KHJvd25hbWVzKHJvd19hbm5vdF94KSxyb3duYW1lcyh4KSkKICB4ID0geFtzaGFyZWQsXQogIHJvd19hbm5vdF94ID0gcm93X2Fubm90X3hbc2hhcmVkLF0KICByb3duYW1lcyh4KSA9IHJvd19hbm5vdF94JG1vdHJwYWNfY29tcF9uYW1lCiAgcmV0dXJuKHgpCn0KCnRhcl92c191bnRhcl9ub3JtX3BhaXJzID0gbGlzdCgKICBjKCJsb2cyLGltcCIsImxvZzIsaW1wLFRNTSIpLAogIGMoImxvZzIsaW1wIiwibG9nMixpbXAiKSwKICBjKCJsb2cyLGltcCIsIm1lZCxsb2csaW1wIiksCiAgYygiaW1wLG5vbmUiLCJpbXAsbm9uZSIpLAogIGMoImltcCxub25lIiwiaW1wLFRNTSIpLAogIGMoImltcCxub25lIiwibWVkLGxvZyxpbXAiKQopCgojIFRoZSBsb29wIGJlbG93IGlzIHRoZSBjb3JlIG9mIHRoZSBjb21wdXRhdGlvbnMgZm9yIGNvbXBhcmluZyAKIyB0YXJnZXRlZCBhbmQgdW50YXJnZXRlZCBkYXRhIHdoZW4gdXNpbmcga25vd24gY29tcG91bmQgbmFtZXMKIwojIEZvciBlYWNoIG5vcm1hbGl6YXRpb24gb3B0aW9uIChhIHBhaXIgZnJvbSB0aGUgbGlzdCBhYm92ZSkgd2UgY29tcHV0ZQojIGFsbCBwYWlyd2lzZSBjb3JyZWxhdGlvbiBtYXRyaWNlcyBvZiB0aGUgb3ZlcmxhcHBpbmcgbWV0YWJvbGl0ZXMgYW5kCiMgdGhlIGRpZmZlcmVudGlhbCBhbmFseXNpcyByZXN1bHRzIChhZ2FpbiBvZiB0aGUgb3ZlcmxhcCkuCiMgVGhlc2Ugb2JqZWN0cyBhcmUgdGhlbiB1c2VkIGxhdGVyIGZvciBvdGhlciBxdWFudGl0YXRpdmUgY29tcGFyaXNvbnMKbm9ybV9tZXRob2QyY29tcGFyaXNvbl9yZXN1bHRzID0gbGlzdCgpCmZvcihub3JtX3BhaXJzIGluIHRhcl92c191bnRhcl9ub3JtX3BhaXJzKXsKICB0YXJfbm9ybV9tZXRob2QgPSBub3JtX3BhaXJzWzFdCiAgdW50YXJfbm9ybV9tZXRob2QgPSBub3JtX3BhaXJzWzJdCiAgc2luZ2xlX21ldGFib2xpdGVfY29ycnMgPSBsaXN0KCkKICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSA9IGMoKQogIG5hbWVkMmNvdmVyZWRfc2hhcmVkX21ldGFib2xpdGVzID0gbGlzdCgpCiAgZm9yKG5uMSBpbiBuYW1lcyhtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzKSl7CiAgICBubjFfdGlzc3VlID0gbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kdGlzc3VlCiAgICBpZighbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kaXNfdGFyZ2V0ZWQpe25leHR9CiAgICBzaW5nbGVfbWV0YWJvbGl0ZV9jb3Jyc1tbbm4xXV0gPSBsaXN0KCkKICAgIG5hbWVkMmNvdmVyZWRfc2hhcmVkX21ldGFib2xpdGVzW1tubjFdXSA9IE5VTEwKICAgIGZvcihubjIgaW4gbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykpewogICAgICBpZihubjIgPT0gbm4xKXtuZXh0fQogICAgICBpZihtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjJdXSRpc190YXJnZXRlZCl7bmV4dH0KICAgICAgbm4yX3Rpc3N1ZSA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMl1dJHRpc3N1ZQogICAgICBubjJfZGF0YXNldCA9IHBhc3RlKHN0cnNwbGl0KG5uMixzcGxpdD0iLCIpW1sxXV1bMzo0XSxjb2xsYXBzZSA9ICIsIikKICAgICAgaWYobm4xX3Rpc3N1ZSE9bm4yX3Rpc3N1ZSl7bmV4dH0KICAgICAgIyBnZXQgdGhlIG51bWVyaWMgZGF0YXNldHMgYW5kIHRoZWlyIGFubm90YXRpb24KICAgICAgeCA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMV1dJG5vcm1hbGl6ZWRfZGF0YVtbdGFyX25vcm1fbWV0aG9kXV0KICAgICAgeSA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMl1dJG5vcm1hbGl6ZWRfZGF0YVtbdW50YXJfbm9ybV9tZXRob2RdXQogICAgICByb3dfYW5ub3RfeCA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMV1dJHJvd19hbm5vdAogICAgICByb3dfYW5ub3RfeSA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW25uMl1dJHJvd19hbm5vdAogICAgICAjIHRyYW5zZm9ybSBtZXRhYm9saXRlIG5hbWVzIHRvIHRoZSBtb3RycGFjIGNvbXAgbmFtZQogICAgICB4ID0gZXh0cmFjdF9ieV9jb21wX25hbWVfZnJvbV9yb3dfYW5ub3QoeCxyb3dfYW5ub3RfeCkKICAgICAgeSA9IGV4dHJhY3RfYnlfY29tcF9uYW1lX2Zyb21fcm93X2Fubm90KHkscm93X2Fubm90X3kpCiAgICAgICMgYWxpZ24gdGhlIHNhbXBsZSBzZXRzCiAgICAgIGJpZF95ID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeSksImJpZCJdCiAgICAgIGJpZF94ID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeCksImJpZCJdICAgIAogICAgICAjIHN0ZXAgMTogbWVyZ2Ugc2FtcGxlcyBmcm9tIHRoZSBzYW1lIEJJRAogICAgICBpZihsZW5ndGgodW5pcXVlKGJpZF94KSkhPWxlbmd0aChiaWRfeCkpewogICAgICAgIHggPSBhZ2dyZWdhdGVfcmVwZWF0ZWRfc2FtcGxlcyh4LGJpZF94KQogICAgICB9CiAgICAgIGVsc2V7CiAgICAgICAgY29sbmFtZXMoeCkgPSBiaWRfeAogICAgICB9CiAgICAgIGlmKGxlbmd0aCh1bmlxdWUoYmlkX3kpKSE9bGVuZ3RoKGJpZF95KSl7CiAgICAgICAgeSA9IGFnZ3JlZ2F0ZV9yZXBlYXRlZF9zYW1wbGVzKHksYmlkX3kpCiAgICAgIH1lbHNlewogICAgICAgIGNvbG5hbWVzKHkpID0gYmlkX3kKICAgICAgfQogICAgICAjIHN0ZXAgMjogdXNlIHRoZSBzaGFyZWQgYmlvIGlkcwogICAgICBzaGFyZWRfYmlkcyA9IGFzLmNoYXJhY3RlcihpbnRlcnNlY3QoY29sbmFtZXMoeSksY29sbmFtZXMoeCkpKQogICAgICB4ID0gYXMubWF0cml4KHhbLHNoYXJlZF9iaWRzXSkKICAgICAgeSA9IGFzLm1hdHJpeCh5WyxzaGFyZWRfYmlkc10pCiAgICAgICMgQXQgdGhpcyBwb2ludCB4IGFuZCB5IGFyZSBvdmVyIHRoZSBzYW1lIEJJRHMsIG5vdyB3ZSBhZGQgdGhlIG1ldGFkYXRhCiAgICAgIHlfbWV0YSA9IAogICAgICAgIHVuaXF1ZShtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjFdXSRzYW1wbGVfbWV0YV9wYXJzZWQpCiAgICAgIHJvd25hbWVzKHlfbWV0YSkgPSB5X21ldGEkYmlkCiAgICAgIHlfbWV0YSA9IHlfbWV0YVtzaGFyZWRfYmlkcyxdCiAgICAgIAogICAgICAjIElmIG9uZSBkYXRhc2V0IGlzIGxvZy10cmFuc2Zvcm1lZCBhbmQgdGhlIG90aGVyIGlzIG5vdAogICAgICAjIHRyYW5zZm9ybSBiYWNrIHRvIHRoZSBvcmlnaW5hbCB2YWx1ZXMKICAgICAgaXNfdGFyX2xvZyA9IGdyZXBsKCJsb2ciLHRhcl9ub3JtX21ldGhvZCkKICAgICAgaXNfdW50YXJfbG9nID0gZ3JlcGwoImxvZyIsdW50YXJfbm9ybV9tZXRob2QpCiAgICAgIGlmKGlzX3Rhcl9sb2cgJiYgIWlzX3VudGFyX2xvZyl7CiAgICAgICAgeCA9IDJeeAogICAgICB9CiAgICAgIGlmKCFpc190YXJfbG9nICYmIGlzX3VudGFyX2xvZyl7CiAgICAgICAgeSA9IDJeeQogICAgICB9CiAgICAKICAgICAgIyBJZiBkYXRhIGFyZSBub3QgbG9nLXRyYW5zZm9ybWVkIHRoZW4gc2NhbGUgdGhlIHJvd3MKICAgICAgaWYoIWlzX3Rhcl9sb2cgfHwgIWlzX3VudGFyX2xvZyl7CiAgICAgICAgeCA9IHQoc2NhbGUodCh4KSxjZW50ZXIgPSBULHNjYWxlID0gVCkpCiAgICAgICAgeSA9IHQoc2NhbGUodCh5KSxjZW50ZXIgPSBULHNjYWxlID0gVCkpCiAgICAgIH0KICAgIAogICAgICAjIGdldCB0aGUgc2hhcmVkIG1hdGVib2xpdGVzCiAgICAgIHNoYXJlZF9tZXRhYm9saXRlcyA9IGludGVyc2VjdChyb3duYW1lcyh4KSxyb3duYW1lcyh5KSkKICAgICAgc2hhcmVkX21ldGFib2xpdGVzID0gbmEub21pdChzaGFyZWRfbWV0YWJvbGl0ZXMpCiAgICAgIGlmKGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpPT0wKXtuZXh0fQogICAgICBuYW1lZDJjb3ZlcmVkX3NoYXJlZF9tZXRhYm9saXRlc1tbbm4xXV0gPSB1bmlvbigKICAgICAgICBuYW1lZDJjb3ZlcmVkX3NoYXJlZF9tZXRhYm9saXRlc1tbbm4xXV0sCiAgICAgICAgc2hhcmVkX21ldGFib2xpdGVzCiAgICAgICkKICAgIAogICAgICAjIENvbXB1dGUgdGhlIGNvcnJlbGF0aW9uIG1hdHJpY2VzIG9mIHRoZSBzaGFyZWQgbWV0YWJvbGl0ZXMKICAgICAgaWYobGVuZ3RoKHNoYXJlZF9tZXRhYm9saXRlcyk+MSl7CiAgICAgICAgICBjb3JycyA9Y29yKHQoeFtzaGFyZWRfbWV0YWJvbGl0ZXMsXSksCiAgICAgICAgICAgICAgICB0KHlbc2hhcmVkX21ldGFib2xpdGVzLF0pLG1ldGhvZCA9ICJzcGVhcm1hbiIpCiAgICAgIH0KICAgICAgZWxzZXsKICAgICAgICAgIGNvcnJzID0gY29yKHhbc2hhcmVkX21ldGFib2xpdGVzLF0sCiAgICAgICAgICAgICAgICB5W3NoYXJlZF9tZXRhYm9saXRlcyxdLG1ldGhvZCA9ICJzcGVhcm1hbiIpCiAgICAgIH0KICAgIAogICAgICAjIHRha2UgdGhlIGNvdmFyaWF0ZXMgKGlnbm9yZSBkaXN0YW5jZXMpCiAgICAgIGN1cnJfY292X2NvbHMgPSBpbnRlcnNlY3QoY29sbmFtZXMoeV9tZXRhKSxiaW9zcGVjX2NvbHNbMl0pCiAgICAgIGN1cnJfY292cyA9IGRhdGEuZnJhbWUoeV9tZXRhWyxjdXJyX2Nvdl9jb2xzXSkKICAgICAgbmFtZXMoY3Vycl9jb3ZzKSA9IGN1cnJfY292X2NvbHMKICAgICAgY3Vycl9jb3ZzJHNleCA9IHlfbWV0YSRhbmltYWwucmVnaXN0cmF0aW9uLnNleCAjIGFkZCBzZXgKICAgIAogICAgICAjIGRpZmZlcmVudGlhbCBhbmFseXNpcwogICAgICBmb3IodHAgaW4gdW5pcXVlKHlfbWV0YSRhbmltYWwua2V5LnRpbWVwb2ludCkpewogICAgICAgIGN1cnJfY29udHJvbF90cCA9IE5VTEwKICAgICAgICAjIGlmKHRwID09IDcgfHwgdHAgPT0gNCl7Y3Vycl9jb250cm9sX3RwPTd9CiAgICAgICAgcmVzeCA9IHQoYXBwbHkoCiAgICAgICAgICBtYXRyaXgoeFtzaGFyZWRfbWV0YWJvbGl0ZXMsXSxucm93PWxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwxLAogICAgICAgICAgcGFzczFhX3NpbXBsZV9kaWZmZXJlbnRpYWxfYWJ1bmRhbmNlLAogICAgICAgICAgdHBzID0geV9tZXRhJGFuaW1hbC5rZXkudGltZXBvaW50LHRwPXRwLAogICAgICAgICAgaXNfY29udHJvbCA9IHlfbWV0YSRhbmltYWwua2V5LmlzX2NvbnRyb2wsCiAgICAgICAgICBjb3ZzID0gY3Vycl9jb3ZzLHJldHVybl9tb2RlbD1GLAogICAgICAgICAgY29udHJvbF90cCA9IGN1cnJfY29udHJvbF90cAogICAgICAgICkpCiAgICAgICAgcmVzeSA9IHQoYXBwbHkoCiAgICAgICAgICBtYXRyaXgoeVtzaGFyZWRfbWV0YWJvbGl0ZXMsXSxucm93PWxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwxLAogICAgICAgICAgcGFzczFhX3NpbXBsZV9kaWZmZXJlbnRpYWxfYWJ1bmRhbmNlLAogICAgICAgICAgdHBzID0geV9tZXRhJGFuaW1hbC5rZXkudGltZXBvaW50LHRwPXRwLAogICAgICAgICAgaXNfY29udHJvbCA9IHlfbWV0YSRhbmltYWwua2V5LmlzX2NvbnRyb2wsCiAgICAgICAgICBjb3ZzID0gY3Vycl9jb3ZzLHJldHVybl9tb2RlbD1GLAogICAgICAgICAgY29udHJvbF90cCA9IGN1cnJfY29udHJvbF90cAogICAgICAgICkpCiAgICAgICAgIyBBZGQgZGF0YXNldCBpbmZvcm1hdGlvbiwgdGltZSBwb2ludCwgdGlzc3VlCiAgICAgICAgIyBUaGVzZSBhcmUgaW1wb3J0YW50IGFubm90YXRpb25zIGZvciBvdXIgc3VtbWFyeSBtYXRyaXgKICAgICAgICAjIGNhbGxlZCBzaW5nbGVfbWV0YWJvbGl0ZV9kZSBiZWxvdwogICAgICAgIGFkZGVkX2NvbHVtbnMgPSBtYXRyaXgoY2JpbmQoCiAgICAgICAgICByZXAobm4xLGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwKICAgICAgICAgIHJlcChubjIsbGVuZ3RoKHNoYXJlZF9tZXRhYm9saXRlcykpLAogICAgICAgICAgc2hhcmVkX21ldGFib2xpdGVzLAogICAgICAgICAgcmVwKHRwLGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKSwKICAgICAgICAgIHJlcChubjFfdGlzc3VlLGxlbmd0aChzaGFyZWRfbWV0YWJvbGl0ZXMpKQogICAgICAgICksbnJvdz1sZW5ndGgoc2hhcmVkX21ldGFib2xpdGVzKSkKICAgICAgICByZXN4ID0gY2JpbmQocmVzeCxyZXAoVCxucm93KHJlc3gpKSkKICAgICAgICBjb2xuYW1lcyhyZXN4KVtuY29sKHJlc3gpXSA9ICJpc190YXJnZXRlZCIKICAgICAgICByZXN5ID0gY2JpbmQocmVzeSxyZXAoRixucm93KHJlc3kpKSkKICAgICAgICBjb2xuYW1lcyhyZXN5KVtuY29sKHJlc3kpXSA9ICJpc190YXJnZXRlZCIKICAgICAgICBpZihucm93KHJlc3gpPjEpewogICAgICAgICAgcmVzeCA9IGNiaW5kKGFkZGVkX2NvbHVtbnNbLC0yXSxyZXN4KQogICAgICAgICAgcmVzeSA9IGNiaW5kKGFkZGVkX2NvbHVtbnNbLC0xXSxyZXN5KQogICAgICAgIH0KICAgICAgICBlbHNlewogICAgICAgICAgcmVzeCA9IGMoYWRkZWRfY29sdW1uc1ssLTJdLHJlc3gpCiAgICAgICAgICByZXN5ID0gYyhhZGRlZF9jb2x1bW5zWywtMV0scmVzeSkKICAgICAgIH0KICAgICAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSA9IHJiaW5kKHNpbmdsZV9tZXRhYm9saXRlX2RlLHJlc3gpCiAgICAgICAgc2luZ2xlX21ldGFib2xpdGVfZGUgPSByYmluZChzaW5nbGVfbWV0YWJvbGl0ZV9kZSxyZXN5KQogICAgICB9CiAgICAgIHNpbmdsZV9tZXRhYm9saXRlX2NvcnJzW1tubjFdXVtbbm4yXV0gPSBjb3JycwogICAgfQogIH0KCiAgIyBSZWZvcm1hdCB0aGUgZGlmZmVyZW50aWFsIGFuYWx5c2lzIHJlc3VsdHMgZm9yIGVhc2llciBjb21wYXJpc29uIGxhdGVyCiAgc2luZ2xlX21ldGFib2xpdGVfZGUgPSBkYXRhLmZyYW1lKHNpbmdsZV9tZXRhYm9saXRlX2RlKQogIG5hbWVzKHNpbmdsZV9tZXRhYm9saXRlX2RlKSA9IGMoImRhdGFzZXQiLCJtZXRhYm9saXRlIiwidHAiLCJ0aXNzdWUiLAogICAgIkVzdCIsIlN0ZCIsIlRzdGF0IiwiUHZhbHVlIiwiaXNfdGFyZ2V0ZWQiKQogIGZvcihjb2wgaW4gbmFtZXMoc2luZ2xlX21ldGFib2xpdGVfZGUpWy1jKDE6NCldKXsKICAgIHNpbmdsZV9tZXRhYm9saXRlX2RlW1tjb2xdXSA9IGFzLm51bWVyaWMoCiAgICAgIGFzLmNoYXJhY3RlcihzaW5nbGVfbWV0YWJvbGl0ZV9kZVtbY29sXV0pKQogIH0KICBmb3IoY29sIGluIG5hbWVzKHNpbmdsZV9tZXRhYm9saXRlX2RlKVsxOjRdKXsKICAgIHNpbmdsZV9tZXRhYm9saXRlX2RlW1tjb2xdXSA9IAogICAgICBhcy5jaGFyYWN0ZXIoc2luZ2xlX21ldGFib2xpdGVfZGVbW2NvbF1dKQogIH0KICAjIFJlbW92ZSBkdXBsaWNhdGlvbnMKICAjIFJvdW5kaW5nIHRoZSBwLXZhbHVlcyAtIG5lY2Vzc2FyeSBmb3IgcmVtb3ZpbmcgZHVwbGljYXRlcwogICMgdXNpbmcgdGhlIHVuaXF1ZSBmdW5jdGlvbgogIHJvd25hbWVzKHNpbmdsZV9tZXRhYm9saXRlX2RlKSA9IE5VTEwKICBmb3Iobm4gaW4gbmFtZXMoc2luZ2xlX21ldGFib2xpdGVfZGUpKXsKICAgIG5kaWcgPSA1CiAgICBpZihncmVwbCgicHZhbCIsbm4saWdub3JlLmNhc2UgPSBUKSl7CiAgICAgIG5kaWcgPSAxMAogICAgfQogICAgaWYoaXMubnVtZXJpYyhzaW5nbGVfbWV0YWJvbGl0ZV9kZVtbbm5dXSkpewogICAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZVtbbm5dXSA9IAogICAgICByb3VuZChzaW5nbGVfbWV0YWJvbGl0ZV9kZVtbbm5dXSxkaWdpdHMgPSBuZGlnKQogICAgfQogIH0KICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSA9IHVuaXF1ZShzaW5nbGVfbWV0YWJvbGl0ZV9kZSkKICAKICAjIEZpbmFsbHksIHN0b3JlIGFsbCB0aGUgcmVzdWx0cyBmb3IgdGhlIGN1cnJlbnQgbm9ybWFsaXphdGlvbiBwYWlyCiAgcGFpcl9uYW1lID0gcGFzdGUodGFyX25vcm1fbWV0aG9kLHVudGFyX25vcm1fbWV0aG9kLHNlcD0iOyIpCiAgbm9ybV9tZXRob2QyY29tcGFyaXNvbl9yZXN1bHRzW1twYWlyX25hbWVdXSA9IGxpc3QoCiAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSA9IHNpbmdsZV9tZXRhYm9saXRlX2RlLAogICAgc2luZ2xlX21ldGFib2xpdGVfY29ycnMgPSBzaW5nbGVfbWV0YWJvbGl0ZV9jb3JycywKICAgIG5hbWVkMmNvdmVyZWRfc2hhcmVkX21ldGFib2xpdGVzID0gbmFtZWQyY292ZXJlZF9zaGFyZWRfbWV0YWJvbGl0ZXMKICApCn0KCmBgYAoKV2UgbmV4dCB0cmFuc2Zvcm0gdGhlIGRhdGEgYWJvdmUgaW50byB0YWJsZXMgdGhhdCBjb250YWluIGRhdGEgZm9yIGVhY2ggY29tYmluYXRpb24gb2YgbWV0YWJvbGl0ZSwgdGltZSBwb2ludCwgYW5kIHRpc3N1ZS4gVGhlc2UgYXJlIHRoZW4gdXNlZCBmb3IgZGlmZmVyZW50IG1ldGEtYW5hbHlzZXM6ICgxKSBhIHNpbXBsZSByYW5kb20gZWZmZWN0cyBhbmFseXNpcywgKDIpIHJhbmRvbSBlZmZlY3RzIHdpdGggYSBiaW5hcnkgY292YXJpYXRlIGluZGljYXRpbmcgaWYgYSBkYXRhc2V0IGlzIHRhcmdldGVkIG9yIHVudGFyZ2V0ZWQsICgzKSByZWRvIHRoZSBSRSBtb2RlbCBvZiAoMSkgd2l0aCB0aGUgdGFyZ2V0ZWQgZGF0YSBvbmx5LCBhbmQgKDQpIHJlZG8gdGhlIFJFIG1vZGVsIG9mICgxKSB3aXRoIHRoZSB1bnRhcmdldGVkIGRhdGEgb25seS4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KCmxpYnJhcnkobWV0YWZvcikKZm9yKHBhaXJfbmFtZSBpbiBuYW1lcyhub3JtX21ldGhvZDJjb21wYXJpc29uX3Jlc3VsdHMpKXsKICBtZXRhX2FuYWx5c2lzX3N0YXRzID0gbGlzdCgpCiAgc2luZ2xlX21ldGFib2xpdGVfZGUgPSAKICAgIG5vcm1fbWV0aG9kMmNvbXBhcmlzb25fcmVzdWx0c1tbcGFpcl9uYW1lXV0kc2luZ2xlX21ldGFib2xpdGVfZGUKICBmb3IodGlzc3VlIGluIHVuaXF1ZShzaW5nbGVfbWV0YWJvbGl0ZV9kZSR0aXNzdWUpKXsKICAgIGZvcih0cCBpbiB1bmlxdWUoc2luZ2xlX21ldGFib2xpdGVfZGUkdHApKXsKICAgICAgY3Vycl9zdWJzZXQgPSBzaW5nbGVfbWV0YWJvbGl0ZV9kZVsKICAgICAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSR0aXNzdWU9PXRpc3N1ZSAmCiAgICAgICAgICBzaW5nbGVfbWV0YWJvbGl0ZV9kZSR0cD09dHAsXQogICAgICBmb3IobWV0YWJvbGl0ZSBpbiB1bmlxdWUoY3Vycl9zdWJzZXQkbWV0YWJvbGl0ZSkpewogICAgICAgIGN1cnJfbWV0X2RhdGEgPSBjdXJyX3N1YnNldFsKICAgICAgICAgIGN1cnJfc3Vic2V0JG1ldGFib2xpdGU9PW1ldGFib2xpdGUsXQogICAgICAgIGN1cnJfbWV0X2RhdGEkdmFyID0gY3Vycl9tZXRfZGF0YSRTdGReMgogICAgICAgIHJlX21vZGVsMSA9IE5VTEw7cmVfbW9kZWwyPU5VTEwKICAgICAgICByZV9tb2RlbF90YXIgPSBOVUxMO3JlX21vZGVsX3VudGFyID0gTlVMTAogICAgICAgIHRyeSh7cmVfbW9kZWwxID0gcm1hLnVuaShjdXJyX21ldF9kYXRhJEVzdCxjdXJyX21ldF9kYXRhJHZhcixtZXRob2Q9IkZFIil9KQogICAgICAgIHRyeSh7cmVfbW9kZWwyID0gcm1hLm12KGN1cnJfbWV0X2RhdGEkRXN0LGN1cnJfbWV0X2RhdGEkdmFyLAogICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RzPWN1cnJfbWV0X2RhdGEkaXNfdGFyZ2V0ZWQsbWV0aG9kPSJGRSIpfSkKICAgICAgICB0cnkoe3JlX21vZGVsX3RhciA9IHJtYS51bmkoCiAgICAgICAgICBjdXJyX21ldF9kYXRhW2N1cnJfbWV0X2RhdGEkaXNfdGFyZ2V0ZWQ9PTEsIkVzdCJdLAogICAgICAgICAgY3Vycl9tZXRfZGF0YVtjdXJyX21ldF9kYXRhJGlzX3RhcmdldGVkPT0xLCJ2YXIiXSwKICAgICAgICAgIG1ldGhvZD0iRkUiCiAgICAgICAgKX0pCiAgICAgICAgdHJ5KHtyZV9tb2RlbF91bnRhciA9IHJtYS51bmkoCiAgICAgICAgICBjdXJyX21ldF9kYXRhW2N1cnJfbWV0X2RhdGEkaXNfdGFyZ2V0ZWQ9PTAsIkVzdCJdLAogICAgICAgICAgY3Vycl9tZXRfZGF0YVtjdXJyX21ldF9kYXRhJGlzX3RhcmdldGVkPT0wLCJ2YXIiXSwKICAgICAgICAgIG1ldGhvZD0iRkUiCiAgICAgICAgKX0pCiAgICAgICAgbWV0YV9hbmFseXNpc19zdGF0c1tbcGFzdGUobWV0YWJvbGl0ZSx0aXNzdWUsdHAsc2VwPSIsIildXSA9IAogICAgICAgICAgbGlzdChjdXJyX21ldF9kYXRhPWN1cnJfbWV0X2RhdGEscmVfbW9kZWwxPXJlX21vZGVsMSwKICAgICAgICAgICAgcmVfbW9kZWwyID0gcmVfbW9kZWwyLHJlX21vZGVsX3Rhcj1yZV9tb2RlbF90YXIsCiAgICAgICAgICAgIHJlX21vZGVsX3VudGFyID0gcmVfbW9kZWxfdW50YXIpCiAgICAgIH0KICAgIH0KICB9CiAgbm9ybV9tZXRob2QyY29tcGFyaXNvbl9yZXN1bHRzW1twYWlyX25hbWVdXSRtZXRhX2FuYWx5c2lzX3N0YXRzID0KICAgIG1ldGFfYW5hbHlzaXNfc3RhdHMKfQoKYGBgCgojIyBUYXJnZXRlZCB2cy4gVW50YXJnZXRlZDogY29tcG91bmQgb3ZlcmxhcCAKCldlIGZpcnN0IHBsb3QgdGhlIG51bWJlciBhbmQgcGVyY2VudGFnZSBvZiBtZXRhYm9saXRlcyBpbiB0aGUgdGFyZ2V0ZWQgZGF0YXNldHMgdGhhdCBhcmUgbWVhc3VyZWQgaW4gYXQgbGVhc3Qgb25lIHVudGFyZ2V0ZWQgZGF0YXNldC4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KbGlicmFyeShnZ3Bsb3QyKQpkYXRhc2V0Mm51bV9tZXRhYm9saXRlcyA9IHNhcHBseShtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeClucm93KHgkc2FtcGxlX2RhdGEpKQpuYW1lZF9kYXRhc2V0X2NvdmVyYWdlID0gc2FwcGx5KG5hbWVkMmNvdmVyZWRfc2hhcmVkX21ldGFib2xpdGVzLGxlbmd0aCkKbmFtZWRfZGF0YXNldF9jb3ZlcmFnZSA9IGRhdGEuZnJhbWUoCiAgbmFtZSA9IG5hbWVzKG5hbWVkX2RhdGFzZXRfY292ZXJhZ2UpLAogIHBlcmNlbnRhZ2UgPSBuYW1lZF9kYXRhc2V0X2NvdmVyYWdlIC8KICBkYXRhc2V0Mm51bV9tZXRhYm9saXRlc1tuYW1lcyhuYW1lZF9kYXRhc2V0X2NvdmVyYWdlKV0sCiAgY291bnQgPSBuYW1lZF9kYXRhc2V0X2NvdmVyYWdlLAogIHRvdGFsID0gZGF0YXNldDJudW1fbWV0YWJvbGl0ZXNbbmFtZXMobmFtZWRfZGF0YXNldF9jb3ZlcmFnZSldCikKIyBhZGQgZGF0YXNldHMgd2l0aCBubyBjb3ZlcmFnZQphbGxfdGFyZ2V0ZWRfZGF0YXNldHMgPSBuYW1lcyhtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzKQphbGxfdGFyZ2V0ZWRfZGF0YXNldHMgPSBhbGxfdGFyZ2V0ZWRfZGF0YXNldHNbIWdyZXBsKCJ1bnRhciIsYWxsX3RhcmdldGVkX2RhdGFzZXRzKV0KemVyb19jb3ZlcmFnZV9kYXRhc2V0cyA9IHNldGRpZmYoYWxsX3RhcmdldGVkX2RhdGFzZXRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lZF9kYXRhc2V0X2NvdmVyYWdlJG5hbWUpCnplcm9fY292ZXJhZ2VfZGF0YXNldHMgPSBkYXRhLmZyYW1lKAogIG5hbWUgPSB6ZXJvX2NvdmVyYWdlX2RhdGFzZXRzLAogIHBlcmNlbnRhZ2UgPSByZXAoMCxsZW5ndGgoemVyb19jb3ZlcmFnZV9kYXRhc2V0cykpLAogIGNvdW50ID0gcmVwKDAsbGVuZ3RoKHplcm9fY292ZXJhZ2VfZGF0YXNldHMpKSwKICB0b3RhbCA9IGRhdGFzZXQybnVtX21ldGFib2xpdGVzW3plcm9fY292ZXJhZ2VfZGF0YXNldHNdCikKbmFtZWRfZGF0YXNldF9jb3ZlcmFnZSA9IHJiaW5kKG5hbWVkX2RhdGFzZXRfY292ZXJhZ2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHplcm9fY292ZXJhZ2VfZGF0YXNldHMpCm5hbWVkX2RhdGFzZXRfY292ZXJhZ2UgPSAKICBuYW1lZF9kYXRhc2V0X2NvdmVyYWdlW29yZGVyKGFzLmNoYXJhY3RlcihuYW1lZF9kYXRhc2V0X2NvdmVyYWdlJG5hbWUpKSxdCnByaW50KGdncGxvdChuYW1lZF9kYXRhc2V0X2NvdmVyYWdlLCBhZXMoeD1uYW1lLCB5PXBlcmNlbnRhZ2UpKSArIAogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLHdpZHRoPTAuMikgKyBjb29yZF9mbGlwKCkgKwogIGdlb21fdGV4dChkYXRhPW5hbWVkX2RhdGFzZXRfY292ZXJhZ2UsIAogICAgICAgICAgICBhZXMobmFtZSwgcGVyY2VudGFnZSswLjA1LCBsYWJlbD1jb3VudCksIAogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoPTAuOSksCiAgICAgICAgICAgIHNpemU9NCkgKyAKICBnZ3RpdGxlKCJUYXJnZXRlZCBkYXRhc2V0OiBjb3ZlcmFnZSBieSB1bnRhcmdldGVkIikpCgpgYGAKCiMjIENvbXBhcmlzb24gcmVzdWx0czogbm9ybWFsaXphdGlvbiBtZXRob2RzCgojIyMgU3BlYXJtYW4gY29ycmVsYXRpb25zCgpDb21wYXJlIHRoZSBub3JtYWxpemF0aW9uIG1ldGhvZHMgYnkgdGhlaXIgY29ycmVsYXRpb24gZGlzdHJpYnV0aW9ucy4KCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KCnJlcF9jb3JyZWxhdGlvbnMgPSBjKCkKdGlzc3VlcyA9IHVuaXF1ZShzYXBwbHkobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cyxmdW5jdGlvbih4KXgkdGlzc3VlKSkKZm9yKHBhaXJfbmFtZSBpbiBuYW1lcyhub3JtX21ldGhvZDJjb21wYXJpc29uX3Jlc3VsdHMpKXsKICBzaW5nbGVfbWV0YWJvbGl0ZV9jb3JycyA9IAogICAgbm9ybV9tZXRob2QyY29tcGFyaXNvbl9yZXN1bHRzW1twYWlyX25hbWVdXSRzaW5nbGVfbWV0YWJvbGl0ZV9jb3JycwogIGZvcih0aXNzdWUgaW4gdGlzc3Vlcyl7CiAgICBjdXJyX2RhdGFzZXRzID0gbmFtZXMoc2luZ2xlX21ldGFib2xpdGVfY29ycnMpWwogICAgICBncmVwbCh0aXNzdWUsbmFtZXMoc2luZ2xlX21ldGFib2xpdGVfY29ycnMpKQogICAgXQogICAgYmV0d2Vlbl9jb3JycyA9IGMoKQogICAgZm9yKHRhcl9kYXRhc2V0IGluIGN1cnJfZGF0YXNldHMpewogICAgICBsID0gc2luZ2xlX21ldGFib2xpdGVfY29ycnNbW3Rhcl9kYXRhc2V0XV0KICAgICAgYmV0d2Vlbl9jb3JycyA9IGMoYmV0d2Vlbl9jb3Jycyx1bm5hbWUodW5saXN0KHNhcHBseShsLGRpYWcpKSkpCiAgICB9CiAgICByZXBfY29ycmVsYXRpb25zID0gcmJpbmQocmVwX2NvcnJlbGF0aW9ucywKICAgICAgICAgIGMocGFpcl9uYW1lLHRpc3N1ZSxtZWFuKGJldHdlZW5fY29ycnMsbmEucm09VCksc2QoYmV0d2Vlbl9jb3JycyxuYS5ybT1UKSkpCiAgfQp9CnJlcF9jb3JyZWxhdGlvbnMgPSByZXBfY29ycmVsYXRpb25zWyFpcy5uYShyZXBfY29ycmVsYXRpb25zWywzXSksXQpyZXBfY29ycmVsYXRpb25zID0gZGF0YS5mcmFtZSgKICAiTm9ybU1ldGhvZCIgPSByZXBfY29ycmVsYXRpb25zWywxXSwKICAiVGlzc3VlIiA9IHJlcF9jb3JyZWxhdGlvbnNbLDJdLAogICJNZWFuIiA9IGFzLm51bWVyaWMocmVwX2NvcnJlbGF0aW9uc1ssM10pLAogICJTRCIgPSBhcy5udW1lcmljKHJlcF9jb3JyZWxhdGlvbnNbLDRdKQopCnJlcF9jb3JyZWxhdGlvbnMgPSByZXBfY29ycmVsYXRpb25zW29yZGVyKHJlcF9jb3JyZWxhdGlvbnMkVGlzc3VlKSxdCnByaW50KAogICAgZ2dwbG90KHJlcF9jb3JyZWxhdGlvbnMsIGFlcyh4PVRpc3N1ZSwgeT1NZWFuLCBmaWxsPU5vcm1NZXRob2QpKSArCiAgICAgIGdlb21fYmFyKHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKCksIHN0YXQ9ImlkZW50aXR5IiwgY29sb3VyPSdibGFjaycpICsKICAgICAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj1NZWFuLVNELCB5bWF4PU1lYW4rU0QpLG5hLnJtPVQsIAogICAgICAgICAgICAgICAgICAgd2lkdGg9LjIscG9zaXRpb249cG9zaXRpb25fZG9kZ2UoLjkpKQopCgpgYGAKCiMjIyBEaWZmZXJlbnRpYWwgYW5hbHlzaXMgcmVzdWx0cwoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpwdGhyID0gMC4wMDEKZm9yKHRpc3N1ZSBpbiB0aXNzdWVzKXsKICBmb3IocGFpcl9uYW1lIGluIG5hbWVzKG5vcm1fbWV0aG9kMmNvbXBhcmlzb25fcmVzdWx0cykpewogICAgICBtZXRhX2FuYWx5c2lzX3N0YXRzID0gCiAgICAgICAgICBub3JtX21ldGhvZDJjb21wYXJpc29uX3Jlc3VsdHNbW3BhaXJfbmFtZV1dJG1ldGFfYW5hbHlzaXNfc3RhdHMKICAgICAgbWV0YV9hbmFseXNpc19zdGF0cyA9IG1ldGFfYW5hbHlzaXNfc3RhdHNbCiAgICAgICAgZ3JlcGwodGlzc3VlLG5hbWVzKG1ldGFfYW5hbHlzaXNfc3RhdHMpKQogICAgICBdCiAgICAgIGlmKGxlbmd0aChtZXRhX2FuYWx5c2lzX3N0YXRzKT09MCl7bmV4dH0KICAgICAgbmFpdmVfYW5hbHlzaXNfdGFyID0gc2FwcGx5KG1ldGFfYW5hbHlzaXNfc3RhdHMsCiAgICAgICAgZnVuY3Rpb24oeClzdW0oeCRjdXJyX21ldF9kYXRhWywiUHZhbHVlIl08cHRociAmCiAgICAgICAgICAgICAgICAgICAgIHgkY3Vycl9tZXRfZGF0YVssImlzX3RhcmdldGVkIl0pKQogICAgICBuYWl2ZV9hbmFseXNpc191bnRhciA9IHNhcHBseShtZXRhX2FuYWx5c2lzX3N0YXRzLAogICAgICAgIGZ1bmN0aW9uKHgpc3VtKHgkY3Vycl9tZXRfZGF0YVssIlB2YWx1ZSJdPHB0aHIgJgogICAgICAgICAgICAgICAgICAgICAheCRjdXJyX21ldF9kYXRhWywiaXNfdGFyZ2V0ZWQiXSkpCiAgICAgIHRiID0gdGFibGUobmFpdmVfYW5hbHlzaXNfdGFyLG5haXZlX2FuYWx5c2lzX3VudGFyKQogICAgICBub25zaWdfaW5fYm90aCA9IHRiWzEsMV0KICAgICAgdGJbMSwxXSA9IDAKICAgICAgYmFycGxvdCh0KHRiKSwKICAgICAgICAgICAgICBsZWdlbmQ9VCx4bGFiPSJOdW1iZXIgb2YgdGFyZ2V0ZWQgZGF0YXNldHMgd2l0aCBwPDAuMDAxIiwKICAgICAgICAgICAgICB5bGFiID0gIm51bWJlciBvZiBtZXRhYm9saXRlcyIsCiAgICAgICAgICAgICAgbWFpbiA9IHBhc3RlKHRpc3N1ZSxwYWlyX25hbWUpKQogICAgICAKICAgICAgd3JpdGUudGFibGUoCiAgICAgICAgbmFtZXMod2hpY2gobmFpdmVfYW5hbHlzaXNfdGFyPjAgJiBuYWl2ZV9hbmFseXNpc191bnRhcj09MCkpLAogICAgICAgIHF1b3RlID0gRixyb3cubmFtZXMgPSBGCiAgICAgICkKICAgICAgd3JpdGUudGFibGUoCiAgICAgICAgbmFtZXMod2hpY2gobmFpdmVfYW5hbHlzaXNfdGFyPT0wICYgbmFpdmVfYW5hbHlzaXNfdW50YXI+MCkpLAogICAgICAgIHF1b3RlID0gRixyb3cubmFtZXMgPSBGCiAgICAgICkKICB9Cn0KYGBgCgoKIyMjIE1ldGEtYW5hbHlzaXMgcmVzdWx0cwoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KFZlbm5EaWFncmFtKQpyZXF1aXJlKGdyaWRFeHRyYSkKcHRociA9IDAuMDAxCnRpc3N1ZXMgPSB1bmlxdWUoc2FwcGx5KG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHMsZnVuY3Rpb24oeCl4JHRpc3N1ZSkpCnRhcl92c191bnRhcl9jb3JyZWxhdGlvbnMgPSBjKCkKZm9yKHRpc3N1ZSBpbiB0aXNzdWVzKXsKICBmb3IocGFpcl9uYW1lIGluIG5hbWVzKG5vcm1fbWV0aG9kMmNvbXBhcmlzb25fcmVzdWx0cykpewogICAgbWV0YV9hbmFseXNpc19zdGF0cyA9IAogICAgICAgIG5vcm1fbWV0aG9kMmNvbXBhcmlzb25fcmVzdWx0c1tbcGFpcl9uYW1lXV0kbWV0YV9hbmFseXNpc19zdGF0cwogICAgbWV0YV9hbmFseXNpc19zdGF0cyAgPSBtZXRhX2FuYWx5c2lzX3N0YXRzWwogICAgICAgIGdyZXBsKHRpc3N1ZSxuYW1lcyhtZXRhX2FuYWx5c2lzX3N0YXRzKSkKICAgICAgXQogICAgaWYoaXMubnVsbChtZXRhX2FuYWx5c2lzX3N0YXRzKSB8fCBsZW5ndGgobWV0YV9hbmFseXNpc19zdGF0cyk9PTApe25leHR9CiAgCiAgICAjIFAtdmFsdWUgZm9yIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGFyZ2V0ZWQgYW5kIHVudGFyZ2V0ZWQKICAgIHRhcmdldGVkX2RpZmZfcCA9IAogICAgc2FwcGx5KG1ldGFfYW5hbHlzaXNfc3RhdHMsZnVuY3Rpb24oeCl4JHJlX21vZGVsMiRwdmFsWzJdKQoKICAgIyBQLXZhbHVlcyAtIHRhcmdldGVkIHZzLiB1bnRhcmdldGVkCiAgICBwdmFsc190YXIgPSBzYXBwbHkobWV0YV9hbmFseXNpc19zdGF0cyxmdW5jdGlvbih4KXgkcmVfbW9kZWxfdGFyJHB2YWwpCiAgICBwdmFsc191bnRhciA9IHNhcHBseShtZXRhX2FuYWx5c2lzX3N0YXRzLGZ1bmN0aW9uKHgpeCRyZV9tb2RlbF91bnRhciRwdmFsKQogICAgcHZhbHNfdW50YXIgPSB1bmxpc3QocHZhbHNfdW50YXJbc2FwcGx5KHB2YWxzX3VudGFyLGxlbmd0aCk+MF0pCiAgICBzaWduaWZpY2FudF9pbiA9IHJlcCgiTm9uZSIsbGVuZ3RoKHB2YWxzX3VudGFyKSkKICAgIHNpZ25pZmljYW50X2luW3B2YWxzX3RhcjxwdGhyXSA9ICJUYXJnZXRlZCIKICAgIHNpZ25pZmljYW50X2luW3B2YWxzX3VudGFyPHB0aHJdID0gIlVudGFyZ2V0ZWQiCiAgICBzaWduaWZpY2FudF9pbltwdmFsc190YXI8cHRociAmIHB2YWxzX3VudGFyPHB0aHJdID0gIkJvdGgiCiAgICBzaWduaWZpY2FudF9kaWZmID0gdGFyZ2V0ZWRfZGlmZl9wPHB0aHIKICAgIHJobyA9IGNvcigtbG9nMTAocHZhbHNfdGFyKSwtbG9nMTAocHZhbHNfdW50YXIpLG1ldGhvZCA9ICJwZWFyc29uIikKICAgICMgQmV0YXMgLSB0YXJnZXRlZCB2cy4gdW50YXJnZXRlZAogICAgYmV0YXNfdGFyID0gCiAgICAgIHNhcHBseShtZXRhX2FuYWx5c2lzX3N0YXRzLGZ1bmN0aW9uKHgpeCRyZV9tb2RlbF90YXIkYmV0YVsxLDFdKQogICAgYmV0YXNfdW50YXIgPSAKICAgICAgc2FwcGx5KG1ldGFfYW5hbHlzaXNfc3RhdHMsZnVuY3Rpb24oeCl4JHJlX21vZGVsX3VudGFyJGJldGFbMSwxXSkKICAgIGJldGFzX3VudGFyID0gdW5saXN0KGJldGFzX3VudGFyW3NhcHBseShiZXRhc191bnRhcixsZW5ndGgpPjBdKQogICAgZGYgPSBkYXRhLmZyYW1lKAogICAgICB0YXJnZXRlZCA9IGJldGFzX3RhciwKICAgICAgdW50YXJnZXRlZCA9IGJldGFzX3VudGFyLAogICAgICBzaWduaWZpY2FudF9pbiA9IHNpZ25pZmljYW50X2luLAogICAgICBzaWduaWZpY2FudF9kaWZmID0gc2lnbmlmaWNhbnRfZGlmZgogICAgKQogICAgcmhvX2JldGEgPSBjb3IoYmV0YXNfdW50YXIsYmV0YXNfdGFyLG1ldGhvZCA9ICJwZWFyc29uIikKICAgIHJob3AgPSBjb3IudGVzdChiZXRhc191bnRhcixiZXRhc190YXIsbWV0aG9kID0gInBlYXJzb24iKSRwLnZhbHVlCiAgICBwcmludCgKICAgICAgZ2dwbG90KGRmLCBhZXMoeD10YXJnZXRlZCwgeT11bnRhcmdldGVkLAogICAgICAgICAgICAgICAgIHNoYXBlPXNpZ25pZmljYW50X2RpZmYsIGNvbG9yPXNpZ25pZmljYW50X2luKSkgKwogICAgICBnZW9tX3BvaW50KCkgKyBnZW9tX2FibGluZShzbG9wZT0xLGludGVyY2VwdCA9IDApICsgCiAgICAgIGdndGl0bGUocGFzdGUodGlzc3VlLCBwYWlyX25hbWUsCiAgICAgICAgICAgICAgICAgICAgImVmZmVjdHMsIHJobz06Iixmb3JtYXQocmhvX2JldGEsZGlnaXRzPTIpKSkKICAgICkKICAKICAgIHRhcl92c191bnRhcl9jb3JyZWxhdGlvbnMgPSByYmluZCh0YXJfdnNfdW50YXJfY29ycmVsYXRpb25zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGModGlzc3VlLHBhaXJfbmFtZSxyaG8scmhvX2JldGEpKQogICAjIGRyYXcgYSB2ZW5uIGRpYWdyYW0KICAgIGludGVyX2FyZWEgPSBzdW0oc2lnbmlmaWNhbnRfaW49PSJCb3RoIikKICAgIHRhcl9hcmVhID0gc3VtKHNpZ25pZmljYW50X2luPT0iVGFyZ2V0ZWQiKSArIGludGVyX2FyZWEKICAgIHVudGFyX2FyZWEgPSBzdW0oc2lnbmlmaWNhbnRfaW49PSJVbnRhcmdldGVkIikgKyBpbnRlcl9hcmVhCiAgICBzdWJ0ID0gcGFzdGUoIk5vdCBzaWduaWZpY2FudCBpbiBib3RoOiIsdGFibGUoc2lnbmlmaWNhbnRfaW4pWyJOb25lIl0pCiAgICAKICAgIHNzID0gcGFzdGUoc2FtcGxlKG5hbWVzKHB2YWxzX3VudGFyKVtzaWduaWZpY2FudF9pbj09IlRhcmdldGVkIl0pWzE6MTBdLGNvbGxhcHNlPSI7IikKICAgIHNzID0gZ3N1YihwYXN0ZSgiLCIsdGlzc3VlLHNlcD0iIiksIiIsc3MpCiAgICBzcwogICAgc3MgPSBwYXN0ZShzYW1wbGUobmFtZXMocHZhbHNfdW50YXIpW3NpZ25pZmljYW50X2luPT0iVW50YXJnZXRlZCJdKVsxOjEwXSxjb2xsYXBzZT0iOyIpCiAgICBzcyA9IGdzdWIocGFzdGUoIiwiLHRpc3N1ZSxzZXA9IiIpLCIiLHNzKQogICAgc3MKICAgIAogICAgdmVubmcgPSBkcmF3LnBhaXJ3aXNlLnZlbm4odGFyX2FyZWEsdW50YXJfYXJlYSxpbnRlcl9hcmVhLAogICAgICAgICAgICBjKCJUYXJnZXRlZCIsIlVudGFyZ2V0ZWQiKSxsdHkgPSByZXAoImJsYW5rIiwyKSwgCiAgICAgICAgICAgIGZpbGwgPSBjKCJwaW5rIiwgImN5YW4iKSwgYWxwaGEgPSByZXAoMC41LCAyKSwKICAgICAgICAgICAgY2F0LmRpc3QgPSByZXAoMC4wMSwgMiksaW5kPUYpCiAgICAjIGdyaWQubmV3cGFnZSgpCiAgICBncmlkLmFycmFuZ2UoZ1RyZWUoY2hpbGRyZW49dmVubmcpLCAKICAgICAgICAgICAgICAgICB0b3A9cGFzdGUodGlzc3VlLHBhaXJfbmFtZSksIGJvdHRvbT1zdWJ0KQogIH0KfQoKCmBgYAoKIyMgQ29tcGFyaXNvbiByZXN1bHRzOiBkZXRhaWxlZCBhbmFseXNpcyBvZiB0aGUgc2VsZWN0ZWQgbm9ybWFsaXphdGlvbgoKIyMjIFNwZWFybWFuIGNvcnJlbGF0aW9ucwoKV2UgZXhhbWluZSB0aGUgYXZlcmFnZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSBwbGF0Zm9ybXMgKHdpdGhpbiB0aXNzdWVzKS4gV2hlbmV2ZXIgdHdvIHBsYXRmb3JtcyBzaGFyZSBtb3JlIHRoYW4gYSBzaW5nbGUgbWV0YWJvbGl0ZSB3ZSBwbG90IGJvdGggdGhlIGF2ZXJhZ2UgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgc2FtZSBtZXRhYm9saXRlcyBhbmQgYmV0d2VlbiBvdGhlciBtZXRhYm9saXRlcy4gQWRkaW5nIHRoZSBhdmVyYWdlIGNvcnJlbGF0aW9uIGJldHdlZW4gcGxhdGZvcm1zIGJ1dCB3aXRoIGRpZmZlcmVudCBtZXRhYm9saXRlcyBpcyBpbXBvcnRhbnQgYXMgaXQgZ2l2ZXMgc29tZSBwZXJzcGVjdGl2ZSB0byB3aGF0IGEgc2lnbmlmaWNhbnQgY29ycmVsYXRpb24gaXMuIFRoYXQgaXMsIGluIG1hbnkgY2FzZXMgYmVsb3csIHRoZSBhdmVyYWdlIGNvcnJlbGF0aW9uIG1heSBiZSBncmVhdGVyIHRoYW4gZXhwZWN0ZWQuCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CiMgTmV4dCBleGFtaW5lIHRoZSBTcGVhcm1hbiBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwbGF0Zm9ybXMKbWVhbl9hYnM8LWZ1bmN0aW9uKHgsLi4uKXtyZXR1cm4obWVhbihhYnMoeCksLi4uKSl9CnNkX2FiczwtZnVuY3Rpb24oeCwuLi4pe3JldHVybihzZChhYnMoeCksLi4uKSl9CmV4dHJhY3RfZGlhZ192c19ub25fZGlhZzwtZnVuY3Rpb24oY29ycnMsZnVuYz1tZWFuLC4uLil7CiAgaWYobGVuZ3RoKGNvcnJzKT09MSl7CiAgICByZXR1cm4oYyhzYW1lPWZ1bmMoY29ycnMsLi4uKSxvdGhlcj1OQSkpCiAgfQogIHNhbWUgPSBmdW5jKGRpYWcoY29ycnMpLC4uLikKICBvdGhlciA9IGZ1bmMoCiAgICBjKGNvcnJzW2xvd2VyLnRyaShjb3JycyxkaWFnID0gRildKSwuLi4pCiAgcmV0dXJuKGMoc2FtZT1zYW1lLG90aGVyPW90aGVyKSkKfQoKc2luZ2xlX21ldGFib2xpdGVfY29ycnMgPQogIG5vcm1fbWV0aG9kMmNvbXBhcmlzb25fcmVzdWx0cyRgbG9nMixpbXA7bG9nMixpbXBgJHNpbmdsZV9tZXRhYm9saXRlX2NvcnJzCmZvcih0YXJfZGF0YXNldCBpbiBuYW1lcyhzaW5nbGVfbWV0YWJvbGl0ZV9jb3JycykpewogIGwgPSBzaW5nbGVfbWV0YWJvbGl0ZV9jb3Jyc1tbdGFyX2RhdGFzZXRdXQogIGlmKGxlbmd0aChsKT09MCl7bmV4dH0KICBjb3JyX2luZm8gPSBhcy5kYXRhLmZyYW1lKHQoc2FwcGx5KGwsIGV4dHJhY3RfZGlhZ192c19ub25fZGlhZykpKQogIGNvcnJfc2QgPSBhcy5kYXRhLmZyYW1lKHQoc2FwcGx5KGwsIGV4dHJhY3RfZGlhZ192c19ub25fZGlhZyxmdW5jPXNkKSkpCiAgIyBzaG9ydGVuIHRoZSByb3cgbmFtZXMKICByb3duYW1lcyhjb3JyX2luZm8pID0gc2FwcGx5KHJvd25hbWVzKGNvcnJfaW5mbyksCiAgICAgIGZ1bmN0aW9uKHgpcGFzdGUoc3Ryc3BsaXQoeCxzcGxpdD0iLCIpW1sxXV1bMzo0XSxjb2xsYXBzZT0iLCIpKQogIHJvd25hbWVzKGNvcnJfc2QpID0gcm93bmFtZXMoY29ycl9pbmZvKQogIGNvcnJfaW5mbyRkYXRhc2V0ID0gcm93bmFtZXMoY29ycl9pbmZvKQogIGNvcnJfc2QkZGF0YXNldCA9IGNvcnJfaW5mbyRkYXRhc2V0CiAgY29ycl9pbmZvID0gbWVsdChjb3JyX2luZm8pCiAgY29ycl9zZCA9IG1lbHQoY29ycl9zZCkKICBjb3JyX2luZm8kc2QgPSBjb3JyX3NkJHZhbHVlCiAgcHJpbnQoCiAgICBnZ3Bsb3QoY29ycl9pbmZvLCBhZXMoeD1kYXRhc2V0LCB5PXZhbHVlLCBmaWxsPXZhcmlhYmxlKSkgKwogICAgICBnZW9tX2Jhcihwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZSgpLCBzdGF0PSJpZGVudGl0eSIsIGNvbG91cj0nYmxhY2snKSArCiAgICAgIGdlb21fZXJyb3JiYXIoYWVzKHltaW49dmFsdWUtc2QsIHltYXg9dmFsdWUrc2QpLG5hLnJtPVQsIAogICAgICAgICAgICAgICAgICAgd2lkdGg9LjIscG9zaXRpb249cG9zaXRpb25fZG9kZ2UoLjkpKSArCiAgICBnZ3RpdGxlKHRhcl9kYXRhc2V0KSArIHhsYWIoIlVudGFyZ2V0ZWQgZGF0YXNldCIpICsgeWxhYigiU3BlYXJtYW4iKSArCiAgICAgIGxhYnMoZmlsbCA9ICJQYWlyIHR5cGUiKSArIAogICAgICB0aGVtZShsZWdlbmQucG9zaXRpb249InRvcCIsbGVnZW5kLmRpcmVjdGlvbiA9ICJob3Jpem9udGFsIikKICApCn0KYGBgCgojIyMgUGxvdCBzZWxlY3RlZCBleGFtcGxlcwoKSGVyZSBhcmUgdGhlIHJlc3VsdHMgZm9yIGxhY3RhdGUgaW4gcGxhc21hLgoKRXhhbXBsZSBmcm9tIGEgbG9nMiBkYXRhc2V0OgoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQptZXRhX2FuYWx5c2lzX3N0YXRzID0gCiAgbm9ybV9tZXRob2QyY29tcGFyaXNvbl9yZXN1bHRzJGBsb2cyLGltcDttZWQsbG9nLGltcGAkbWV0YV9hbmFseXNpc19zdGF0cwpsYWN0X3JlcyA9IG1ldGFfYW5hbHlzaXNfc3RhdHNbCiAgZ3JlcGwoImxhY3QiLG5hbWVzKG1ldGFfYW5hbHlzaXNfc3RhdHMpLGlnbm9yZS5jYXNlID0gVCkgJgogICAgZ3JlcGwoInBsYXNtYSIsbmFtZXMobWV0YV9hbmFseXNpc19zdGF0cyksaWdub3JlLmNhc2UgPSBUKQpdCmxhY3RfcmVzX2hvdXJzID0gc2FwcGx5KG5hbWVzKGxhY3RfcmVzKSwKICAgICAgICAgICAgZnVuY3Rpb24oeClhcy5udW1lcmljKHN0cnNwbGl0KHgsc3BsaXQ9IiwiKVtbMV1dWzNdKSkKbGFjdF9yZXMgPSBsYWN0X3Jlc1tvcmRlcihsYWN0X3Jlc19ob3VycyldCmZvcihsYWN0X2V4YW1wbGUgaW4gbmFtZXMobGFjdF9yZXMpWzE6Nl0pewogIGN1cnJfbGFiZWxzID0gZ3N1YigicGxhc21hLCIsIiIsCiAgICAgICAgICAgICAgICAgICAgIG1ldGFfYW5hbHlzaXNfc3RhdHNbW2xhY3RfZXhhbXBsZV1dW1sxXV1bLDFdKQogIGZvcmVzdChtZXRhX2FuYWx5c2lzX3N0YXRzW1tsYWN0X2V4YW1wbGVdXSRyZV9tb2RlbDEsCiAgICAgICBzbGFiID0gY3Vycl9sYWJlbHMsCiAgICAgICBtYWluID0gbGFjdF9leGFtcGxlLHhsYWIgPSAiTG9nIGZjIiwKICAgICAgIGNvbCA9ICJibHVlIixjZXggPSAxLjEpCn0KCmN1cnJfbGFiZWxzID0gZ3N1YigicGxhc21hLCIsIiIsCiAgICAgICAgICAgICAgICAgICAgIG1ldGFfYW5hbHlzaXNfc3RhdHNbWyJnbHV0YW1hdGUscGxhc21hLDAiXV1bWzFdXVssMV0pCmN1cnJfbGFiZWxzID0gY3Vycl9sYWJlbHNbCiAgbWV0YV9hbmFseXNpc19zdGF0c1tbImdsdXRhbWF0ZSxwbGFzbWEsMCJdXVtbMV1dJGlzX3RhcmdldGVkPT0wXQpmb3Jlc3QobWV0YV9hbmFseXNpc19zdGF0c1tbImdsdXRhbWF0ZSxwbGFzbWEsMCJdXSRyZV9tb2RlbF91bnRhciwKICAgICAgICBzbGFiID0gY3Vycl9sYWJlbHMsCiAgICAgICBtYWluID0gImdsdXRhbWF0ZSxwbGFzbWEsMCIseGxhYiA9ICJMb2cgZmMiLAogICAgICAgY29sID0gImJsdWUiLGNleCA9IDEuMSkKCmBgYAoKRXhhbXBsZSBmcm9tIGEgc2NhbGVkIGRhdGFzZXQ6CgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9Cm1ldGFfYW5hbHlzaXNfc3RhdHMgPSAKICBub3JtX21ldGhvZDJjb21wYXJpc29uX3Jlc3VsdHMkYGltcCxub25lO2ltcCxub25lYCRtZXRhX2FuYWx5c2lzX3N0YXRzCmxhY3RfcmVzID0gbWV0YV9hbmFseXNpc19zdGF0c1sKICBncmVwbCgibGFjdCIsbmFtZXMobWV0YV9hbmFseXNpc19zdGF0cyksaWdub3JlLmNhc2UgPSBUKSAmCiAgICBncmVwbCgicGxhc21hIixuYW1lcyhtZXRhX2FuYWx5c2lzX3N0YXRzKSxpZ25vcmUuY2FzZSA9IFQpCl0KbGFjdF9yZXNfaG91cnMgPSBzYXBwbHkobmFtZXMobGFjdF9yZXMpLAogICAgICAgICAgICBmdW5jdGlvbih4KWFzLm51bWVyaWMoc3Ryc3BsaXQoeCxzcGxpdD0iLCIpW1sxXV1bM10pKQpsYWN0X3JlcyA9IGxhY3RfcmVzW29yZGVyKGxhY3RfcmVzX2hvdXJzKV0KZm9yKGxhY3RfZXhhbXBsZSBpbiBuYW1lcyhsYWN0X3JlcylbMTo2XSl7CiAgY3Vycl9sYWJlbHMgPSBnc3ViKCJwbGFzbWEsIiwiIiwKICAgICAgICAgICAgICAgICAgICAgbWV0YV9hbmFseXNpc19zdGF0c1tbbGFjdF9leGFtcGxlXV1bWzFdXVssMV0pCiAgZm9yZXN0KG1ldGFfYW5hbHlzaXNfc3RhdHNbW2xhY3RfZXhhbXBsZV1dJHJlX21vZGVsMSwKICAgICAgIHNsYWIgPSBjdXJyX2xhYmVscywKICAgICAgIG1haW4gPSBsYWN0X2V4YW1wbGUseGxhYiA9ICJMb2cgZmMiLAogICAgICAgY29sID0gImJsdWUiLGNleCA9IDEuMSkKfQoKYGBgCgpXZSBjYW4gbm93IGNoZWNrIHRoZSBzYW1lIGFuYWx5c2lzIGZvciBsaXZlcjoKCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KbGFjdF9yZXMgPSBtZXRhX2FuYWx5c2lzX3N0YXRzWwogIGdyZXBsKCJsYWN0IixuYW1lcyhtZXRhX2FuYWx5c2lzX3N0YXRzKSxpZ25vcmUuY2FzZSA9IFQpICYKICAgIGdyZXBsKCJsaXZlciIsbmFtZXMobWV0YV9hbmFseXNpc19zdGF0cyksaWdub3JlLmNhc2UgPSBUKQpdCmxhY3RfcmVzX2hvdXJzID0gc2FwcGx5KG5hbWVzKGxhY3RfcmVzKSwKICAgICAgICAgICAgZnVuY3Rpb24oeClhcy5udW1lcmljKHN0cnNwbGl0KHgsc3BsaXQ9IiwiKVtbMV1dWzNdKSkKbGFjdF9yZXMgPSBsYWN0X3Jlc1tvcmRlcihsYWN0X3Jlc19ob3VycyldCmZvcihsYWN0X2V4YW1wbGUgaW4gbmFtZXMobGFjdF9yZXMpWzE6Nl0pewogIGN1cnJfbGFiZWxzID0gZ3N1YigibGl2ZXJfcG93ZGVyLCIsIiIsCiAgICAgICAgICAgICAgICAgICAgIG1ldGFfYW5hbHlzaXNfc3RhdHNbW2xhY3RfZXhhbXBsZV1dW1sxXV1bLDFdKQogIGZvcmVzdChtZXRhX2FuYWx5c2lzX3N0YXRzW1tsYWN0X2V4YW1wbGVdXSRyZV9tb2RlbDEsCiAgICAgICBzbGFiID0gY3Vycl9sYWJlbHMsCiAgICAgICBtYWluID0gbGFjdF9leGFtcGxlLHhsYWIgPSAiTG9nIGZjIiwKICAgICAgIGNvbCA9ICJibHVlIixjZXggPSAxLjEpCn0KCmBgYAoKCjwhLS0gV2UgY3JlYXRlIHJlcG9ydCBieSB0aXNzdWUgd2l0aCBhbGwgZm9yZXN0IHBsb3RzIGFuZCBhbmFseXNpcyByZXN1bHRzLiAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIG1ldGFfYW5hbHlzaXNfdGlzc3VlcyA9IHNhcHBseShuYW1lcyhtZXRhX2FuYWx5c2lzX3N0YXRzKSwgLS0+CjwhLS0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpc3Ryc3BsaXQoeCxzcGxpdD0iLCIpW1sxXV1bMl0pIC0tPgo8IS0tIG1ldGFfYW5hbHlzaXNfbWV0YWJvbGl0ZXMgPSBzYXBwbHkobmFtZXMobWV0YV9hbmFseXNpc19zdGF0cyksIC0tPgo8IS0tICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KXN0cnNwbGl0KHgsc3BsaXQ9IiwiKVtbMV1dWzFdKSAtLT4KCjwhLS0gZm9yKHRpc3N1ZSBpbiB1bmlxdWUobWV0YV9hbmFseXNpc190aXNzdWVzKSl7IC0tPgo8IS0tICAgcGRmKHBhc3RlKCJ+L0Rlc2t0b3AvIix0aXNzdWUsIi5wZGYiLHNlcD0iIikpIC0tPgo8IS0tIH0gLS0+Cgo8IS0tIGBgYCAtLT4KCgoKCkZyb20gdGhlIHBsb3RzIGFib3ZlIHdlIHRha2UgdGhlIG1vc3QgZXh0cmVtZSBleGFtcGxlcyBhbmQgZXhhbWluZSB0aGVpciBmb3Jlc3QgcGxvdHMuCgpgYGB7cixvdXQuaGVpZ2h0PSc1MCUnLG91dC53aWR0aD0nNTAlJyxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9Cm1ldGFfYW5hbHlzaXNfc3RhdHMgPSAKICBub3JtX21ldGhvZDJjb21wYXJpc29uX3Jlc3VsdHMkYGltcCxub25lO2ltcCxub25lYCRtZXRhX2FuYWx5c2lzX3N0YXRzCgojIFAtdmFsdWUgZm9yIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGFyZ2V0ZWQgYW5kIHVudGFyZ2V0ZWQKdGFyZ2V0ZWRfZGlmZl9wID0gCiAgc2FwcGx5KG1ldGFfYW5hbHlzaXNfc3RhdHMsZnVuY3Rpb24oeCl4JHJlX21vZGVsMiRwdmFsWzJdKQoKIyBQLXZhbHVlcyAtIHRhcmdldGVkIHZzLiB1bnRhcmdldGVkCnB2YWxzX3RhciA9IHNhcHBseShtZXRhX2FuYWx5c2lzX3N0YXRzLGZ1bmN0aW9uKHgpeCRyZV9tb2RlbF90YXIkcHZhbCkKcHZhbHNfdW50YXIgPSBzYXBwbHkobWV0YV9hbmFseXNpc19zdGF0cyxmdW5jdGlvbih4KXgkcmVfbW9kZWxfdW50YXIkcHZhbCkKcHZhbHNfdW50YXIgPSB1bmxpc3QocHZhbHNfdW50YXJbc2FwcGx5KHB2YWxzX3VudGFyLGxlbmd0aCk+MF0pCgoKYWdyZWVfZXhhbXBsZSA9IG5hbWVzKHNhbXBsZSh3aGljaChwdmFsc190YXI8IDFlLTUgJiBwdmFsc191bnRhciA8IDFlLTUgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0ZWRfZGlmZl9wID4gMC4xKSlbMV0pCnNpbXBsaWZ5X2xhYmVsc19mb3JfZm9yZXN0PC1mdW5jdGlvbihzKXsKICBzID0gZ3N1YigiLHVudGFyZ2V0ZWQiLCIiLHMpCiAgdGlzc3VlID0gc3Ryc3BsaXQocyxzcGxpdD0iLCIpW1sxXV1bMV0KICBzID0gZ3N1YihwYXN0ZSh0aXNzdWUsIiwiLHNlcD0iIiksIiIscykKICByZXR1cm4ocykKfQpmb3Jlc3QobWV0YV9hbmFseXNpc19zdGF0c1tbYWdyZWVfZXhhbXBsZV1dJHJlX21vZGVsMSwKICBzbGFiID0gc2ltcGxpZnlfbGFiZWxzX2Zvcl9mb3Jlc3QoCiAgICBtZXRhX2FuYWx5c2lzX3N0YXRzW1thZ3JlZV9leGFtcGxlXV1bWzFdXVssMV0pLAogIG1haW4gPSBwYXN0ZShhZ3JlZV9leGFtcGxlLCJzaWduaWZpY2FudCBpbiBib3RoLCB0YXIgYW5kIHVudGFyIGFncmVlIixzZXA9IlxuIiksCiAgeGxhYiA9ICJMb2cgZmMiLGNvbCA9ICJibHVlIikKCmFncmVlX3BfZGlzYWdyZWVfYmV0YSA9IG5hbWVzKHNhbXBsZSh3aGljaChwdmFsc190YXI8IDFlLTUgJiBwdmFsc191bnRhciA8IDFlLTUgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0ZWRfZGlmZl9wIDwgMC4wMDEpKVsxXSkKZm9yZXN0KG1ldGFfYW5hbHlzaXNfc3RhdHNbW2FncmVlX3BfZGlzYWdyZWVfYmV0YV1dJHJlX21vZGVsMSwKICBzbGFiID0gc2ltcGxpZnlfbGFiZWxzX2Zvcl9mb3Jlc3QoCiAgICBtZXRhX2FuYWx5c2lzX3N0YXRzW1thZ3JlZV9wX2Rpc2FncmVlX2JldGFdXVtbMV1dWywxXSksCiAgbWFpbiA9IHBhc3RlKGFncmVlX3BfZGlzYWdyZWVfYmV0YSwKICAgICAgICAgICAgICAgInNpZ25pZmljYW50IGluIGJvdGgsIHRhciBhbmQgdW50YXIgZGlzYWdyZWUiLHNlcD0iXG4iKSwKICB4bGFiID0gIkxvZyBmYyIsY29sID0gImJsdWUiKQoKZGlzYWdyZWVfZXhhbXBsZTEgPSBuYW1lcyhzYW1wbGUod2hpY2gocHZhbHNfdGFyPCAxZS0xMCAmIHB2YWxzX3VudGFyID4wLjEpKVsxXSkKZm9yZXN0KG1ldGFfYW5hbHlzaXNfc3RhdHNbW2Rpc2FncmVlX2V4YW1wbGUxXV0kcmVfbW9kZWwxLAogIHNsYWIgPSBzaW1wbGlmeV9sYWJlbHNfZm9yX2ZvcmVzdCgKICAgIG1ldGFfYW5hbHlzaXNfc3RhdHNbW2Rpc2FncmVlX2V4YW1wbGUxXV1bWzFdXVssMV0pLAogIG1haW4gPSBwYXN0ZShkaXNhZ3JlZV9leGFtcGxlMSwKICAgICAgICAgICAgICAgInNpZ25pZmljYW50IHRhcmdldGVkLCB0YXIgYW5kIHVudGFyIGRpc2FncmVlIixzZXA9IlxuIiksCiAgeGxhYiA9ICJMb2cgZmMiLGNvbCA9ICJibHVlIikKCgpkaXNhZ3JlZV9leGFtcGxlMiA9IG5hbWVzKHNhbXBsZSh3aGljaChwdmFsc190YXIgPiAwLjEgJiBwdmFsc191bnRhciA8IDFlLTIwKSlbMV0pCmZvcmVzdChtZXRhX2FuYWx5c2lzX3N0YXRzW1tkaXNhZ3JlZV9leGFtcGxlMl1dJHJlX21vZGVsMSwKICBzbGFiID0gc2ltcGxpZnlfbGFiZWxzX2Zvcl9mb3Jlc3QoCiAgICBtZXRhX2FuYWx5c2lzX3N0YXRzW1tkaXNhZ3JlZV9leGFtcGxlMl1dW1sxXV1bLDFdKSwKICBtYWluID0gcGFzdGUoZGlzYWdyZWVfZXhhbXBsZTIsCiAgICAgICAgICAgICAgICJzaWduaWZpY2FudCBpbiB1bnRhcmdldGVkLCB0YXIgYW5kIHVudGFyIGRpc2FncmVlIixzZXA9IlxuIiksCiAgeGxhYiA9ICJMb2cgZmMiLGNvbCA9ICJibHVlIikKCmBgYAoKCiMgVGFyZ2V0ZWQgdnMuIHVudGFyZ2V0ZWQ6IGNvbXBhcmlzb24gYXMgYSBwcmVkaWN0aW9uIHRhc2sKClVzZSA1LWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiBmb3IgYW5hbHlzaXMgd2l0aGluIHRpc3N1ZXMuIEZvciBlYWNoIHBhaXIgb2YgdGFyZ2V0ZWQgYW5kIHVudGFyZ2V0ZWQgZGF0YXNldHMgZnJvbSB0aGUgc2FtZSB0aXNzdWUsIHdlIHVzZSB0aGUgdW50YXJnZXRlZCBkYXRhIGFzIHRoZSBwcmVkaWN0aXZlIGZlYXR1cmVzIGFuZCBhbGwgbWV0YWJvbGl0ZXMgaW4gdGhlIHRhcmdldGVkIGRhdGFzZXRzIGFzIHRoZSBkZXBlbmRlbnQgdmFyaWFibGVzLiBUaGUgY29kZSBiZWxvdyB1c2VzIGZlYXR1cmUgc2VsZWN0aW9uIGFuZCByYW5kb20gZm9yZXN0cyB0byB0cmFpbiB0aGUgcHJlZGljdGl2ZSBtb2RlbHMuIAoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsZXZhbD1GQUxTRSxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9Cm5mb2xkcyA9IDUKcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzID0gbGlzdCgpCmZvcihubjEgaW4gbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykpewogIG5uMV90aXNzdWUgPSBzdHJzcGxpdChubjEsc3BsaXQ9IiwiKVtbMV1dWzFdCiAgbm4xX3Rpc3N1ZSA9IGdzdWIoIl9wb3dkZXIiLCIiLG5uMV90aXNzdWUpCiAgaWYoZ3JlcGwoInVudGFyZ2V0ZWQiLG5uMSkpe25leHR9CiAgZm9yKG5uMiBpbiBuYW1lcyhtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzKSl7CiAgICBpZihubjIgPT0gbm4xKXtuZXh0fQogICAgaWYoIWdyZXBsKCJ1bnRhcmdldGVkIixubjIpKXtuZXh0fQogICAgbm4yX3Rpc3N1ZSA9IHN0cnNwbGl0KG5uMixzcGxpdD0iLCIpW1sxXV1bMV0KICAgIG5uMl90aXNzdWUgPSBnc3ViKCJfcG93ZGVyIiwiIixubjJfdGlzc3VlKQogICAgbm4yX2RhdGFzZXQgPSBzdHJzcGxpdChubjIsc3BsaXQ9IiwiKVtbMV1dWzJdCiAgICBpZihubjFfdGlzc3VlIT1ubjJfdGlzc3VlKXtuZXh0fQogICAgcHJpbnQocGFzdGUoImZlYXR1cmVzIGZyb206IixubjIpKQogICAgcHJpbnQocGFzdGUoImxhYmVscyBmcm9tOiIsbm4xKSkKICAgICMgZ2V0IHRoZSBudW1lcmljIGRhdGFzZXRzIGFuZCB0aGVpciBhbm5vdGF0aW9uCiAgICB5ID0gbWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kbm9ybWFsaXplZF9kYXRhW1sxXV0KICAgIHggPSBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjJdXSRub3JtYWxpemVkX2RhdGFbWzFdXQogICAgIyBhbGlnbiB0aGUgc2FtcGxlIHNldHMKICAgIGJpZF95ID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeSksImJpZCJdCiAgICBiaWRfeCA9IG1lcmdlZF9kbWFxY19kYXRhW2NvbG5hbWVzKHgpLCJiaWQiXSAgICAKICAgICMgc3RlcCAxOiBtZXJnZSBzYW1wbGVzIGZyb20gdGhlIHNhbWUgQklECiAgICBpZihsZW5ndGgodW5pcXVlKGJpZF94KSkhPWxlbmd0aChiaWRfeCkpewogICAgICB4ID0gYWdncmVnYXRlX3JlcGVhdGVkX3NhbXBsZXMoeCxiaWRfeCkKICAgIH0KICAgIGVsc2V7CiAgICAgIGNvbG5hbWVzKHgpID0gYmlkX3gKICAgIH0KICAgIGlmKGxlbmd0aCh1bmlxdWUoYmlkX3kpKSE9bGVuZ3RoKGJpZF95KSl7CiAgICAgIHkgPSBhZ2dyZWdhdGVfcmVwZWF0ZWRfc2FtcGxlcyh5LGJpZF95KQogICAgfWVsc2V7CiAgICAgIGNvbG5hbWVzKHkpID0gYmlkX3kKICAgIH0KICAgICMgc3RlcCAyOiB1c2UgdGhlIHNoYXJlZCBiaW8gaWRzCiAgICBzaGFyZWRfYmlkcyA9IGFzLmNoYXJhY3RlcihpbnRlcnNlY3QoY29sbmFtZXMoeSksY29sbmFtZXMoeCkpKQogICAgeCA9IHQoYXMubWF0cml4KHhbLHNoYXJlZF9iaWRzXSkpCiAgICB5ID0gdChhcy5tYXRyaXgoeVssc2hhcmVkX2JpZHNdKSkKICAgICMgQXQgdGhpcyBwb2ludCB4IGFuZCB5IGFyZSBvdmVyIHRoZSBzYW1lIEJJRHMsIG5vdyB3ZSBhZGQgdGhlIG1ldGFkYXRhCiAgICB5X21ldGEgPSB1bmlxdWUobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0c1tbbm4xXV0kc2FtcGxlX21ldGFfcGFyc2VkKQogICAgcm93bmFtZXMoeV9tZXRhKSA9IHlfbWV0YSRiaWQKICAgIHlfbWV0YSA9IHlfbWV0YVtzaGFyZWRfYmlkcyxdCiAgICAKICAgICMgdGFrZSB0aGUgY292YXJpYXRlcyAoaWdub3JlIGRpc3RhbmNlcykKICAgIGN1cnJfY292X2NvbHMgPSBpbnRlcnNlY3QoY29sbmFtZXMoeV9tZXRhKSxiaW9zcGVjX2NvbHNbMl0pCiAgICBjdXJyX2NvdnMgPSBkYXRhLmZyYW1lKHlfbWV0YVssY3Vycl9jb3ZfY29sc10pCiAgICBuYW1lcyhjdXJyX2NvdnMpID0gY3Vycl9jb3ZfY29scwogICAgY3Vycl9jb3ZzJHNleCA9IHlfbWV0YSRhbmltYWwucmVnaXN0cmF0aW9uLnNleCAjIGFkZCBzZXgKICAgICMgYWRkIHRoZSBjb3ZhcmlhdGVzIGludG8geAogICAgeCA9IGNiaW5kKHgsY3Vycl9jb3ZzKQogICAgCiAgICAjIFJ1biB0aGUgcmVncmVzc2lvbnMKICAgIGZvbGRzID0gc2FtcGxlKHJlcCgxOm5mb2xkcywoMStucm93KHgpL25mb2xkcykpKVsxOm5yb3coeCldCiAgICBudW1GZWF0dXJlcyA9IG1pbihuY29sKHgpLDIwMDApCiAgICBwcmVkcyA9IGMoKTtyZWFsPWMoKQogICAgZm9yKGkgaW4gMTpuY29sKHkpKXsKICAgICAgaWYoIGkgJSUgMTAgPT0gMCl7cHJpbnQocGFzdGUoImFuYWx5emluZyBtZXRhYm9saXRlIG51bWJlcjoiLGkpKX0KICAgICAgeV9pID0geVssMV0KICAgICAgaV9wcmVkcyA9IGMoKTtpX3JlYWw9YygpCiAgICAgIGZvcihqIGluIDE6bmZvbGRzKXsKICAgICAgICB0cl94ID0geFtmb2xkcyE9aixdCiAgICAgICAgdHJfeWkgPSB5X2lbZm9sZHMhPWpdCiAgICAgICAgdGVfeCA9IHhbZm9sZHM9PWosXQogICAgICAgIHRlX3kgPSB5X2lbZm9sZHM9PWpdCiAgICAgICAgIyByYW5kb20gZm9yZXN0CiAgICAgICAgIyBtb2RlbCA9IHJhbmRvbUZvcmVzdCh0cl95aSx4PXRyX3gsbnRyZWUgPSAyMCkKICAgICAgICAjIHRlX3ByZWRzID0gcHJlZGljdChtb2RlbCxuZXdkYXRhID0gdGVfeCkKICAgICAgICBtb2RlbCA9IGZlYXR1cmVfc2VsZWN0aW9uX3dyYXBwZXIodHJfeCx0cl95aSwKICAgICAgICAgICAgICAgICAgIGNvZWZmX29mX3ZhcixyYW5kb21Gb3Jlc3QsCiAgICAgICAgICAgICAgICAgICB0b3BLID0gbnVtRmVhdHVyZXMsbnRyZWU9NTApCiAgICAgICAgdGVfcHJlZHMgPSBwcmVkaWN0KG1vZGVsLG5ld2RhdGEgPSB0ZV94KQogICAgICAgIGlfcHJlZHMgPSBjKGlfcHJlZHMsdGVfcHJlZHMpCiAgICAgICAgaV9yZWFsID0gYyhpX3JlYWwsdGVfeSkKICAgICAgfQogICAgICBwcmVkcyA9IGNiaW5kKHByZWRzLGlfcHJlZHMpCiAgICAgIHJlYWwgPSBjYmluZChyZWFsLGlfcmVhbCkKICAgIH0KICAgIGNvbG5hbWVzKHByZWRzKSA9IGNvbG5hbWVzKHkpCiAgICBjb2xuYW1lcyhyZWFscykgPSBjb2xuYW1lcyh5KQogICAgY3Vycm5hbWUgPSBwYXN0ZShubjEsbm4yLHNlcD0iOyIpCiAgICBwcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHNbW2N1cnJuYW1lXV0gPSBsaXN0KAogICAgICBwcmVkcyA9IHByZWRzLHJlYWw9cmVhbAogICAgKQogIH0KfQpzYXZlX3RvX2J1Y2tldChwcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHMsCiAgICAgICAgICAgICAgIGZpbGU9InRhcl92c191bnRhcl9wcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHMuUkRhdGEiLAogICAgICAgICAgICAgICBidWNrZXQgPSAiZ3M6Ly9iaWNfZGF0YV9hbmFseXNpcy9wYXNzMWEvbWV0YWJvbG9taWNzLyIpCmBgYAoKV2Ugbm93IHRha2UgdGhlIHByZWRpY3RlZCBhbmQgcmVhbCB2YWx1ZXMgYW5kIGVzdGltYXRlIHRoZSBwcmVkaWN0aW9uIGFjY3VyYWN5IGluIGRpZmZlcmVudCB3YXlzLiAKCmBgYHtyLG91dC5oZWlnaHQ9JzUwJScsb3V0LndpZHRoPSc1MCUnLGV2YWw9RkFMU0UsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQoKcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzID0gIGxvYWRfZnJvbV9idWNrZXQoCiAgInRhcl92c191bnRhcl9wcmVkaWN0aW9uX2FuYWx5c2lzX3Jlc3VsdHMuUkRhdGEiLAogICAgImdzOi8vYmljX2RhdGFfYW5hbHlzaXMvcGFzczFhL21ldGFib2xvbWljcy8iLEYpCnByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0cyA9IHByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0c1tbMV1dCgpyZXN1bHRzX21ldHJpY3MgPSBsaXN0KCkKZm9yKG5uIGluIG5hbWVzKHByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0cykpewogIHByZWRzID0gcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzW1tubl1dJHByZWRzCiAgcmVhbCA9IHByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0c1tbbm5dXSRyZWFsCiAgdGFyX25hbWUgPSBzdHJzcGxpdChubixzcGxpdD0iOyIpW1sxXV1bMV0KICB1bnRhcl9uYW1lID0gc3Ryc3BsaXQobm4sc3BsaXQ9IjsiKVtbMV1dWzJdCiAgeSA9IG1ldGFib2xvbWljc19wcm9jZXNzZWRfZGF0YXNldHNbW3Rhcl9uYW1lXV0kbm9ybWFsaXplZF9kYXRhW1sxXV0KICBjb2xuYW1lcyhwcmVkcykgPSByb3duYW1lcyh5KQogIGNvbG5hbWVzKHJlYWwpID0gcm93bmFtZXMoeSkKICB0YXJfbmFtZSA9IHNpbXBsaWZ5X21ldGFiX2RhdGFzZXRfbmFtZSh0YXJfbmFtZSkKICB1bnRhcl9uYW1lID0gc2ltcGxpZnlfbWV0YWJfZGF0YXNldF9uYW1lKHVudGFyX25hbWUpCiAgY3VycnRpc3N1ZSA9IHN0cnNwbGl0KHRhcl9uYW1lLHNwbGl0PSIsIilbWzFdXVsxXQogIHRhcl9uYW1lID0gZ3N1YihwYXN0ZShjdXJydGlzc3VlLCIsIixzZXA9IiIpLCIiLHRhcl9uYW1lKQogIHVudGFyX25hbWUgPSBnc3ViKHBhc3RlKGN1cnJ0aXNzdWUsIiwiLHNlcD0iIiksIiIsdW50YXJfbmFtZSkKICBpZighIGN1cnJ0aXNzdWUgJWluJSBuYW1lcyhyZXN1bHRzX21ldHJpY3MpKXsKICAgIHJlc3VsdHNfbWV0cmljc1tbY3VycnRpc3N1ZV1dID0gbGlzdCgpCiAgfQogIGlmKCEgdGFyX25hbWUgJWluJSBuYW1lcyhyZXN1bHRzX21ldHJpY3NbW2N1cnJ0aXNzdWVdXSkpewogICAgcmVzdWx0c19tZXRyaWNzW1tjdXJydGlzc3VlXV1bW3Rhcl9uYW1lXV0gPSBsaXN0KCkKICB9CiAgCiAgcmhvcyA9IGZvcm1hdChkaWFnKGNvcihwcmVkcyxyZWFsLG1ldGhvZD0ic3BlYXJtYW4iKSksZGlnaXRzPTMpCiAgcmhvcyA9IGFzLm51bWVyaWMocmhvcykKICBTRXMgPSBjb2xTdW1zKChwcmVkcy1yZWFsKV4yKQogIE1TRXMgPSBTRXMgLyBucm93KHByZWRzKQogIFJNU0UgPSBzcXJ0KE1TRXMpCiAgck1TRSA9IE1TRXMgLyBhcHBseSh5LDEsdmFyKQogIENvVnMgPSBhcHBseSh5LDEsc2QpIC8gYXBwbHkoeSwxLG1lYW4pCiAgZGlzY0NvVnMgPSBjdXQoQ29WcyxicmVha3MgPSAyLG9yZGVyZWRfcmVzdWx0ID0gVCkKICAKICByZXN1bHRzX21ldHJpY3NbW2N1cnJ0aXNzdWVdXVtbdGFyX25hbWVdXVtbdW50YXJfbmFtZV1dID0gZGF0YS5mcmFtZSgKICAgIHJob3MsTVNFcyxSTVNFLHJNU0UsQ29WcyxkaXNjQ29WcwogICkKfQoKYGBgCgpXZSBub3cgcHJlc2VudCBhIGZldyBzdW1tYXJ5IHBsb3RzLgoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsZXZhbD1GQUxTRSxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CmZvcih0aXNzdWUgaW4gbmFtZXMocmVzdWx0c19tZXRyaWNzKSl7CiAgZm9yKHRhciBpbiBuYW1lcyhyZXN1bHRzX21ldHJpY3NbW3Rpc3N1ZV1dKSl7CiAgICBsID0gcmVzdWx0c19tZXRyaWNzW1t0aXNzdWVdXVtbdGFyXV0KICAgIHJob192c19jdiA9IGMoKQogICAgZm9yKHVudGFyIGluIG5hbWVzKGwpKXsKICAgICAgbSA9IGxbW3VudGFyXV1bLGMoInJob3MiLCJkaXNjQ29WcyIpXSAjIHRha2UgdGhlIGN1cnJlbnQgbWF0cml4CiAgICAgIG0gPSBjYmluZChyZXAodW50YXIsbnJvdyhtKSksbSkKICAgICAgbSRkaXNjQ29WcyA9IGFzLm51bWVyaWMobSRkaXNjQ29WcykKICAgICAgcmhvX3ZzX2N2ID0gcmJpbmQocmhvX3ZzX2N2LG0pCiAgICB9CiAgICBjb2xuYW1lcyhyaG9fdnNfY3YpWzFdID0gImRhdGFzZXQiCiAgICBib3hwbG90KHJob3N+ZGlzY0NvVnM6ZGF0YXNldCxkYXRhPXJob192c19jdixsYXM9MiwKICAgICAgICAgICAgeWxhYj0iU3BlYXJtYW4iLHhsYWIgPSAiIix5bGltPWMoMCwxKSwKICAgICAgICAgICAgbWFpbiA9IHBhc3RlKHRpc3N1ZSx0YXIsc2VwPSIsIikpCiAgfQp9CgoKYGBgCgpBcyBhZGRpdGlvbmFsIHJlZmVyZW5jZXMsIHdlIHRyYWluIGJlbG93IGFkZGl0aW9uYWwgbW9kZWxzLiBGaXJzdCwgd2UgY2hlY2sgdGhlIHByZWRpY3Rpb24gb2YgbmFpdmUgbW9kZWxzIHRoYXQgdXNlIHRlY2huaWNhbCBhbmQgY2xpbmljYWwgY292YXJpYXRlcyBvbmx5LiBTZWNvbmQsIHdlIHVzZSBtdWx0aS10YXNrIHJlZ3Jlc3Npb24gYW5kIGRlZXAgbGVhcm5pbmcgbW9kZWxzLgoKYGBge3Isb3V0LmhlaWdodD0nNTAlJyxvdXQud2lkdGg9JzUwJScsZXZhbD1GQUxTRSxtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9Cgpjb3ZfcHJlZGljdGlvbl9hbmFseXNpc19yZXN1bHRzID0gbGlzdCgpCmZvcihubjEgaW4gbmFtZXMobWV0YWJvbG9taWNzX3Byb2Nlc3NlZF9kYXRhc2V0cykpewogIG5uMV90aXNzdWUgPSBzdHJzcGxpdChubjEsc3BsaXQ9IiwiKVtbMV1dWzFdCiAgbm4xX3Rpc3N1ZSA9IGdzdWIoIl9wb3dkZXIiLCIiLG5uMV90aXNzdWUpCiAgaWYoZ3JlcGwoInVudGFyZ2V0ZWQiLG5uMSkpe25leHR9CiAgcHJpbnQobm4xKQogIHkgPSBtZXRhYm9sb21pY3NfcHJvY2Vzc2VkX2RhdGFzZXRzW1tubjFdXSRub3JtYWxpemVkX2RhdGFbWzFdXQogIHlfdmlhbHMgPSBjb2xuYW1lcyh5KQogIGJpZF95ID0gbWVyZ2VkX2RtYXFjX2RhdGFbY29sbmFtZXMoeSksImJpZCJdCiAgY29sbmFtZXMoeSkgPSBiaWRfeQogIHkgPSB0KGFzLm1hdHJpeCh5KSkKICBpZihuY29sKHkpPjEwMDApe25leHR9CiAgY292X2NvbHMgPSBjKCJhbmltYWwucmVnaXN0cmF0aW9uLnNleCIsCiAgICAgICAgICAgICAiYWN1dGUudGVzdC53ZWlnaHQiLAogICAgICAgICAgICAgImFjdXRlLnRlc3QuZGlzdGFuY2UiLAogICAgICAgICAgICAgImFuaW1hbC5rZXkudGltZXBvaW50IikKICBjb3ZzID0gbWVyZ2VkX2RtYXFjX2RhdGFbeV92aWFscyxjb3ZfY29sc10KICB4ID0gY292cwogIAogICMgUnVuIHRoZSByZWdyZXNzaW9ucwogIGZvbGRzID0gc2FtcGxlKHJlcCgxOm5mb2xkcywoMStucm93KHgpL25mb2xkcykpKVsxOm5yb3coeCldCiAgbnVtRmVhdHVyZXMgPSBtaW4obmNvbCh4KSwyMDAwKQogIHByZWRzID0gYygpO3JlYWw9YygpCiAgZm9yKGkgaW4gMTpuY29sKHkpKXsKICAgIHlfaSA9IHlbLDFdCiAgICBpX3ByZWRzID0gYygpO2lfcmVhbD1jKCkKICAgIGZvcihqIGluIDE6bmZvbGRzKXsKICAgICAgcHJpbnQoaikKICAgICAgdHJfeCA9IHhbZm9sZHMhPWosXQogICAgICB0cl95aSA9IHlfaVtmb2xkcyE9al0KICAgICAgdGVfeCA9IHhbZm9sZHM9PWosXQogICAgICB0ZV95ID0geV9pW2ZvbGRzPT1qXQogICAgICAjIHJhbmRvbSBmb3Jlc3QKICAgICAgbW9kZWwgPSByYW5kb21Gb3Jlc3QodHJfeWkseD10cl94LG50cmVlID0gMjApCiAgICAgIHRlX3ByZWRzID0gcHJlZGljdChtb2RlbCxuZXdkYXRhID0gdGVfeCkKICAgICAgaV9wcmVkcyA9IGMoaV9wcmVkcyx0ZV9wcmVkcykKICAgICAgaV9yZWFsID0gYyhpX3JlYWwsdGVfeSkKICAgIH0KICAgIHByZWRzID0gY2JpbmQocHJlZHMsaV9wcmVkcykKICAgIHJlYWwgPSBjYmluZChyZWFsLGlfcmVhbCkKICB9CiAgY292X3ByZWRpY3Rpb25fYW5hbHlzaXNfcmVzdWx0c1tbbm4xXV0gPSBsaXN0KAogICAgICBwcmVkcyA9IHByZWRzLHJlYWw9cmVhbAogICAgKQp9CgojIHByZWRzID0gYygpO3JlYWw9YygpCiMgZm9yKGogaW4gMTpuZm9sZHMpewojICAgdHJfeCA9IHhbZm9sZHMhPWosXQojICAgdHJfeSA9IHlbZm9sZHMhPWosXQojICAgdGVfeCA9IHhbZm9sZHM9PWosXQojICAgdGVfeSA9IHlbZm9sZHM9PWosXQojICAgbW9kZWwgPSBNVExfd3JhcHBlcih0cl94LHRyX3ksdHlwZT0iUmVncmVzc2lvbiIsIFJlZ3VsYXJpemF0aW9uPSJMMjEiKQojICAgdGVfcHJlZHMgPSBwcmVkaWN0KG1vZGVsLHRlX3gpCiMgICByZWFsID0gcmJpbmQocmVhbCx0ZV95KQojICAgcHJlZHMgPSByYmluZChwcmVkcyx0ZV9wcmVkcykKIyB9CiMgZGlhZyhjb3IocHJlZHMscmVhbCkpCgojIFVzaW5nIFBMUyByZWdyZXNzaW9uCiMgbGlicmFyeShwbHMpCiMgcGxzX21vZGVsID0gcGxzcih5fngsbmNvbXAgPSA1LHZhbGlkYXRpb249IkxPTyIpCiMgZXZhbCA9IE1TRVAocGxzX21vZGVsKQojIAojIHlfcGNhID0gcHJjb21wKHkpCiMgcGxvdCh5X3BjYSkKIyBleHBsYWluZWRfdmFyID0geV9wY2Ekc2Rldl4yL3N1bSh5X3BjYSRzZGV2XjIpCiMgeV9wY2FfbWF0cml4ID0geV9wY2EkeFssMToxMF0KIyAKIyAjIHJlZ3Jlc3Mgb3V0IHNleCwgd2VpZ2h0CiMgCiMgZ2V0X2V4cGxhaW5lZF92YXJpYW5jZV91c2luZ19QQ0EoeCx5KQojIHggPSBhcHBseSh4LDIscmVncmVzc19vdXQsY292cz1jb3ZzKQojIHkgPSBhcHBseSh5LDIscmVncmVzc19vdXQsY292cz1jb3ZzKQojIGdldF9leHBsYWluZWRfdmFyaWFuY2VfdXNpbmdfUENBKHgseSkKCgpgYGAKCgoKCgoKCgoKCgoKCg==